2666. Allow One Function Call
Problem Description
You need to create a higher-order function called once
that takes a function fn
as input and returns a new modified version of that function. The modified function should have special behavior regarding how many times it can execute:
Key Requirements:
- The returned function should only execute the original function
fn
the first time it's called - When called for the first time, it should return the exact same result that
fn
would return with the given arguments - On all subsequent calls (second, third, etc.), the function should return
undefined
without executingfn
at all
Example Behavior:
If you have a function that adds three numbers:
fn = (a, b, c) => (a + b + c)
- Create a "once" version:
onceFn = once(fn)
- First call:
onceFn(1, 2, 3)
returns6
(executes the addition) - Second call:
onceFn(2, 3, 6)
returnsundefined
(doesn't execute the addition) - Any further calls also return
undefined
This pattern is useful when you want to ensure a function's side effects or computations only happen once, regardless of how many times the function is invoked. Common use cases include initialization functions, event handlers that should only fire once, or preventing duplicate API calls.
The solution uses a closure with a boolean flag called
to track whether the function has been executed. When called
is false
, the function executes and sets the flag to true
. All subsequent calls check this flag and return undefined
without executing the original function.
Intuition
The core challenge here is creating a function that "remembers" whether it has been called before. This immediately suggests we need some form of persistent state that survives between function calls.
Think about what we need to track: has the function been called or not? This is a simple yes/no question, which naturally maps to a boolean variable. We need this boolean to persist across multiple invocations of our returned function.
This leads us to the concept of a closure - a function that has access to variables from its outer (enclosing) scope even after the outer function has returned. When we return a function from once()
, that returned function can still access and modify variables defined in once()
's scope.
Here's the thought process:
- We need a flag (
called
) that starts asfalse
- This flag must be accessible to the returned function but not directly modifiable from outside
- When the returned function executes, it checks this flag:
- If
false
: Run the original function, save its result, flip the flag totrue
, and return the result - If
true
: Don't run anything, just returnundefined
- If
The beauty of this approach is that each call to once()
creates its own independent closure with its own called
variable. So if you create multiple "once" versions of different functions (or even the same function), they each maintain their own state independently.
This pattern leverages JavaScript's closure mechanism to create a simple state machine with two states: "not yet called" and "already called". The transition happens exactly once, and there's no way to reset it - perfectly matching our requirements.
Solution Approach
Let's walk through the implementation step by step:
1. Function Signature Setup:
function once(fn: Function): OnceFn
The once
function accepts any function fn
as a parameter and returns a new function of type OnceFn
that can handle JSONValue
arguments.
2. Creating the Closure State:
let called = false;
Inside once
, we declare a boolean variable called
initialized to false
. This variable will live in the closure and track whether the function has been invoked.
3. Returning the Modified Function:
return function (...args) {
// function body
}
We return an anonymous function that accepts any number of arguments using the rest parameter syntax ...args
. This returned function has access to both called
and the original fn
through closure.
4. Implementing the Conditional Logic:
if (!called) { called = true; return fn(...args); }
Inside the returned function:
- We check if
called
is stillfalse
(using!called
) - If it's the first call:
- Set
called = true
to mark that the function has been executed - Call the original function
fn
with all the provided arguments using spread syntaxfn(...args)
- Return the result from
fn
- Set
- If
called
is alreadytrue
(subsequent calls):- The
if
block is skipped - The function implicitly returns
undefined
(JavaScript functions returnundefined
by default when no explicit return statement is reached)
- The
Key Implementation Details:
- Closure Mechanism: Each call to
once()
creates a new closure with its owncalled
variable, ensuring different "once-ified" functions don't interfere with each other - Spread/Rest Operators: The
...args
pattern allows the wrapper function to accept any number of arguments and pass them through to the original function unchanged - Implicit
undefined
Return: No need for an explicitelse
block since JavaScript functions automatically returnundefined
when no return value is specified
This implementation is both memory-efficient (only stores a single boolean) and performant (simple boolean check on each call).
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to see how the once
function works:
Step 1: Create a simple function to wrap
const greet = (name, time) => {
console.log(`Good ${time}, ${name}!`);
return `Greeted ${name}`;
};
Step 2: Create a "once" version using our implementation
const greetOnce = once(greet); // At this point, a closure is created with called = false
Step 3: First invocation
const result1 = greetOnce("Alice", "morning"); // Inside greetOnce: // - Check: called is false ✓ // - Set: called = true // - Execute: greet("Alice", "morning") // - Console outputs: "Good morning, Alice!" // - Returns: "Greeted Alice" // result1 = "Greeted Alice"
Step 4: Second invocation (with different arguments)
const result2 = greetOnce("Bob", "evening"); // Inside greetOnce: // - Check: called is true ✗ // - Skip the if block // - No console output // - Returns: undefined // result2 = undefined
Step 5: All subsequent invocations
const result3 = greetOnce("Charlie", "afternoon"); // Same as step 4: // - called is still true // - Returns: undefined // result3 = undefined
Visualizing the closure state:
Initial state: called = false After first call: called = true (permanently changed) All future calls: called = true (no further changes)
This example demonstrates how the closure maintains the called
flag across multiple invocations, ensuring the original function only executes once while all subsequent calls return undefined
.
Solution Implementation
1from typing import Any, Callable, Optional, Union, List, Dict
2
3# Type definition for JSON values - can be null, boolean, number, string, array of JSON values, or object with JSON values
4JSONValue = Union[None, bool, int, float, str, List['JSONValue'], Dict[str, 'JSONValue']]
5
6def once(fn: Callable) -> Callable:
7 """
8 Creates a wrapper function that ensures the provided function is called at most once.
9 Subsequent calls to the wrapped function will return None without executing the original function.
10
11 Args:
12 fn: The function to be wrapped and called only once
13
14 Returns:
15 A new function that executes fn only on the first call
16 """
17 # Flag to track whether the function has been called
18 has_been_called = False
19
20 def wrapper(*args: JSONValue) -> Optional[JSONValue]:
21 """
22 Inner function that maintains access to has_been_called and fn through closure.
23
24 Args:
25 *args: Variable number of JSON-compatible arguments
26
27 Returns:
28 The result of fn on first call, None on subsequent calls
29 """
30 nonlocal has_been_called
31
32 # Check if this is the first call
33 if not has_been_called:
34 # Mark the function as called
35 has_been_called = True
36 # Execute the original function with provided arguments and return its result
37 return fn(*args)
38
39 # Return None for all subsequent calls
40 return None
41
42 # Return the wrapper function
43 return wrapper
44
45
46# Example usage:
47# def fn(a: int, b: int, c: int) -> int:
48# return a + b + c
49#
50# once_fn = once(fn)
51#
52# print(once_fn(1, 2, 3)) # Returns 6
53# print(once_fn(2, 3, 6)) # Returns None without calling fn
54
1import java.util.*;
2import java.util.function.Function;
3
4/**
5 * Solution class for creating a function that can only be called once
6 */
7class Solution {
8
9 /**
10 * Interface representing a function that accepts variable arguments and returns an Object.
11 * The Object can be null, Boolean, Number, String, List, or Map representing JSON values.
12 */
13 @FunctionalInterface
14 interface OnceFn {
15 Object apply(Object... args);
16 }
17
18 /**
19 * Interface representing the original function to be wrapped.
20 */
21 @FunctionalInterface
22 interface OriginalFunction {
23 Object apply(Object... args);
24 }
25
26 /**
27 * Creates a wrapper function that ensures the provided function is called at most once.
28 * Subsequent calls to the wrapped function will return null without executing the original function.
29 *
30 * @param fn The function to be wrapped and called only once
31 * @return A new function that executes fn only on the first call
32 */
33 public OnceFn once(OriginalFunction fn) {
34 // Create a wrapper class to maintain state
35 class OnceWrapper {
36 // Flag to track whether the function has been called
37 private boolean hasBeenCalled = false;
38
39 // Method that implements the once behavior
40 public Object execute(Object... args) {
41 // Check if this is the first call
42 if (!hasBeenCalled) {
43 // Mark the function as called
44 hasBeenCalled = true;
45 // Execute the original function with provided arguments and return its result
46 return fn.apply(args);
47 }
48 // Return null for all subsequent calls
49 return null;
50 }
51 }
52
53 // Create an instance of the wrapper
54 OnceWrapper wrapper = new OnceWrapper();
55
56 // Return a lambda that delegates to the wrapper's execute method
57 return args -> wrapper.execute(args);
58 }
59
60 /**
61 * Example usage:
62 * OriginalFunction fn = (args) -> {
63 * int a = (int) args[0];
64 * int b = (int) args[1];
65 * int c = (int) args[2];
66 * return a + b + c;
67 * };
68 * OnceFn onceFn = once(fn);
69 *
70 * onceFn.apply(1, 2, 3); // Returns 6
71 * onceFn.apply(2, 3, 6); // Returns null without calling fn
72 */
73}
74
1#include <functional>
2#include <memory>
3#include <any>
4#include <vector>
5#include <optional>
6
7/**
8 * Creates a wrapper function that ensures the provided function is called at most once.
9 * Subsequent calls to the wrapped function will return an empty optional without executing the original function.
10 *
11 * @tparam ReturnType - The return type of the function to be wrapped
12 * @tparam Args - The parameter types of the function to be wrapped
13 * @param fn - The function to be wrapped and called only once
14 * @returns A new function that executes fn only on the first call
15 */
16template<typename ReturnType, typename... Args>
17std::function<std::optional<ReturnType>(Args...)> once(std::function<ReturnType(Args...)> fn) {
18 // Shared pointer to track whether the function has been called
19 // Using shared_ptr to handle the lifetime correctly when the lambda is copied
20 auto hasBeenCalled = std::make_shared<bool>(false);
21
22 // Return a closure that maintains access to hasBeenCalled and fn
23 return [hasBeenCalled, fn](Args... args) -> std::optional<ReturnType> {
24 // Check if this is the first call
25 if (!(*hasBeenCalled)) {
26 // Mark the function as called
27 *hasBeenCalled = true;
28 // Execute the original function with provided arguments and return its result
29 return fn(args...);
30 }
31 // Return empty optional for all subsequent calls
32 return std::nullopt;
33 };
34}
35
36/**
37 * Example usage:
38 * auto fn = [](int a, int b, int c) { return a + b + c; };
39 * auto onceFn = once<int, int, int, int>(std::function<int(int, int, int)>(fn));
40 *
41 * auto result1 = onceFn(1, 2, 3); // Returns optional containing 6
42 * auto result2 = onceFn(2, 3, 6); // Returns empty optional without calling fn
43 */
44
1// Type definition for JSON values - can be null, boolean, number, string, array of JSON values, or object with JSON values
2type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
3
4// Type definition for the returned function that can only be called once
5type OnceFn = (...args: JSONValue[]) => JSONValue | undefined;
6
7/**
8 * Creates a wrapper function that ensures the provided function is called at most once.
9 * Subsequent calls to the wrapped function will return undefined without executing the original function.
10 *
11 * @param fn - The function to be wrapped and called only once
12 * @returns A new function that executes fn only on the first call
13 */
14function once(fn: Function): OnceFn {
15 // Flag to track whether the function has been called
16 let hasBeenCalled: boolean = false;
17
18 // Return a closure that maintains access to hasBeenCalled and fn
19 return function (...args: JSONValue[]): JSONValue | undefined {
20 // Check if this is the first call
21 if (!hasBeenCalled) {
22 // Mark the function as called
23 hasBeenCalled = true;
24 // Execute the original function with provided arguments and return its result
25 return fn(...args) as JSONValue;
26 }
27 // Return undefined for all subsequent calls
28 return undefined;
29 };
30}
31
32/**
33 * Example usage:
34 * let fn = (a: number, b: number, c: number) => (a + b + c)
35 * let onceFn = once(fn)
36 *
37 * onceFn(1, 2, 3); // Returns 6
38 * onceFn(2, 3, 6); // Returns undefined without calling fn
39 */
40
Time and Space Complexity
Time Complexity: O(1)
for the wrapper function execution. The actual time complexity depends on the wrapped function fn
, but the overhead introduced by the once
wrapper is constant - it only performs a boolean check and potentially updates a boolean flag.
Space Complexity: O(1)
for the closure created by the once
function. The wrapper maintains only a single boolean variable called
in its closure scope. The space required doesn't grow with input size, making it constant space. However, if we consider the wrapped function fn
being stored in the closure, it's still O(1)
additional space overhead relative to the original function.
Detailed Analysis:
- The
once
function creates a closure that captures two variables: the boolean flagcalled
and the original functionfn
- Each call to the returned function performs:
- One boolean check (
if (!called)
) - Potentially one boolean assignment (
called = true
) - Potentially one function call (
fn(...args)
)
- One boolean check (
- All these operations are constant time
O(1)
- The memory overhead is fixed regardless of how many times the returned function is called or the size of arguments passed
Common Pitfalls
1. Shared State Across Different Instances
A common mistake is accidentally sharing the called
flag between different instances of once-wrapped functions. This happens when the flag is placed in the wrong scope:
Incorrect Implementation:
# WRONG: Global flag shared by all once-wrapped functions
has_been_called = False
def once(fn):
def wrapper(*args):
global has_been_called
if not has_been_called:
has_been_called = True
return fn(*args)
return None
return wrapper
# Problem demonstration:
add = once(lambda x, y: x + y)
multiply = once(lambda x, y: x * y)
print(add(2, 3)) # Returns 5
print(multiply(4, 5)) # Returns None (Wrong! Should return 20)
Solution: Keep the flag in the closure scope (as shown in the correct implementation) so each wrapped function has its own independent state.
2. Mutable Default Arguments Confusion
When wrapping functions with mutable default arguments, developers might incorrectly assume the once
wrapper affects the default argument behavior:
def append_item(lst=[]):
lst.append(1)
return lst
once_append = once(append_item)
# The once wrapper doesn't prevent the mutable default argument issue
result1 = once_append() # Returns [1]
result2 = once_append() # Returns None, but the original function's default list still exists
Solution: Be aware that once
only controls execution frequency, not the underlying function's behavior with mutable defaults.
3. Expecting Cached Return Values
A frequent misconception is thinking that once
caches the first result and returns it on subsequent calls:
What developers expect (but doesn't happen):
expensive_computation = once(lambda x: x ** 1000000) result1 = expensive_computation(2) # Returns large number result2 = expensive_computation(3) # They expect the same large number, but get None
Solution: If you need caching behavior, implement a memoization wrapper instead:
def once_with_cache(fn):
has_been_called = False
cached_result = None
def wrapper(*args):
nonlocal has_been_called, cached_result
if not has_been_called:
has_been_called = True
cached_result = fn(*args)
return cached_result
return wrapper
4. Thread Safety Issues
In multi-threaded environments, the basic implementation isn't thread-safe. Two threads could both pass the if not has_been_called
check before either sets it to True
:
import threading
counter = 0
def increment():
global counter
counter += 1
return counter
once_increment = once(increment)
# Multiple threads calling simultaneously
threads = [threading.Thread(target=once_increment) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
# counter might be > 1 due to race condition
Solution: Use thread synchronization:
import threading
def thread_safe_once(fn):
lock = threading.Lock()
has_been_called = False
def wrapper(*args):
nonlocal has_been_called
with lock:
if not has_been_called:
has_been_called = True
return fn(*args)
return None
return wrapper
5. Loss of Function Metadata
The wrapper function loses the original function's metadata (name, docstring, etc.):
def important_function():
"""This function does something important."""
return 42
wrapped = once(important_function)
print(wrapped.__name__) # Prints 'wrapper', not 'important_function'
print(wrapped.__doc__) # Prints wrapper's docstring, not original
Solution: Use functools.wraps
decorator:
from functools import wraps
def once(fn):
has_been_called = False
@wraps(fn) # Preserves original function metadata
def wrapper(*args):
nonlocal has_been_called
if not has_been_called:
has_been_called = True
return fn(*args)
return None
return wrapper
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!