Facebook Pixel

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:

  1. The returned function should only execute the original function fn the first time it's called
  2. When called for the first time, it should return the exact same result that fn would return with the given arguments
  3. On all subsequent calls (second, third, etc.), the function should return undefined without executing fn 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) returns 6 (executes the addition)
  • Second call: onceFn(2, 3, 6) returns undefined (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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. We need a flag (called) that starts as false
  2. This flag must be accessible to the returned function but not directly modifiable from outside
  3. When the returned function executes, it checks this flag:
    • If false: Run the original function, save its result, flip the flag to true, and return the result
    • If true: Don't run anything, just return undefined

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 still false (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 syntax fn(...args)
    • Return the result from fn
  • If called is already true (subsequent calls):
    • The if block is skipped
    • The function implicitly returns undefined (JavaScript functions return undefined by default when no explicit return statement is reached)

Key Implementation Details:

  • Closure Mechanism: Each call to once() creates a new closure with its own called 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 explicit else block since JavaScript functions automatically return undefined 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 Evaluator

Example 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 flag called and the original function fn
  • Each call to the returned function performs:
    • One boolean check (if (!called))
    • Potentially one boolean assignment (called = true)
    • Potentially one function call (fn(...args))
  • 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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More