LeetCode 2666 - Allow One Function Call

This problem asks us to create a wrapper around an existing function fn such that the wrapped version can only execute the original function one time. In other words, we are given a function fn, and we must return a new function.

LeetCode Problem 2666

Difficulty: 🟢 Easy
Topics:

Solution

Problem Understanding

This problem asks us to create a wrapper around an existing function fn such that the wrapped version can only execute the original function one time.

In other words, we are given a function fn, and we must return a new function. This returned function behaves exactly like fn during its first invocation, meaning it accepts the same arguments and returns the same result. However, after that first successful execution, every future call must immediately return undefined without calling fn again.

The key requirement is that the original function must only be executed at most once. This means we need some way to remember whether the function has already been called.

The input consists of a function fn, which can take arbitrary arguments. The examples show functions receiving multiple parameters, but our solution must work for any number of arguments because JavaScript functions are flexible. The returned function should preserve the same argument behavior by forwarding all received arguments to fn.

The expected output is not a direct value, but rather a modified function. When this returned function is called for the first time, it executes fn and returns the result. Every later call returns undefined.

The constraints are very small. The number of calls is between 1 and 10, and argument lists are relatively short. Because of this, efficiency is not a major concern. However, the problem is testing understanding of function closures and state persistence, not algorithmic performance.

There are several important edge cases to consider. The wrapped function may be called only once, meaning we should still return the proper result without issue. The wrapped function may also receive different arguments on later calls, but those arguments should be ignored because fn must never execute again. Another important detail is that fn could return any valid JavaScript value, including 0, false, or null, so we cannot determine whether the function was already called by checking the return value. Instead, we must explicitly track execution state.

Approaches

Brute Force Approach

A brute-force way to solve this problem is to store all previous invocations in a collection and check whether the function has already been executed before running it again.

For example, we could maintain an array of previous calls. Each time the returned function executes, we check whether this array is empty. If it is empty, we call fn, save information about the invocation, and return the result. Otherwise, we return undefined.

This approach is correct because it ensures that fn only executes once. However, it uses unnecessary storage and bookkeeping. Since we only care whether the function has already been called, storing invocation history is excessive.

Optimal Approach

The key observation is that we only need one piece of information: whether fn has already been executed.

A simple boolean variable is sufficient. We can initialize a flag such as called = false. The returned function checks this flag whenever it runs:

  • If called is false, mark it as true, execute fn, and return the result.
  • If called is already true, return undefined.

This works because closures allow the returned function to remember variables from the surrounding scope, even after the outer function has finished executing. The boolean state persists between calls.

Approach Time Complexity Space Complexity Notes
Brute Force O(1) O(1) Stores unnecessary call history or extra bookkeeping
Optimal O(1) O(1) Uses a closure with a boolean flag

Although both approaches have constant complexity in practice, the optimal solution is cleaner and uses only the minimum necessary state.

Algorithm Walkthrough

  1. Define a boolean variable, such as called, and initialize it to false. This variable tracks whether fn has already executed.
  2. Return a new function that accepts arbitrary arguments. This ensures the wrapper behaves like the original function regardless of parameter count.
  3. Inside the returned function, check the called flag. If it is already true, immediately return undefined. This prevents future executions of fn.
  4. If called is false, set it to true before calling fn. Updating the flag first guarantees that even if fn behaves unexpectedly, future calls remain blocked.
  5. Execute fn using the provided arguments and return its result.

Why it works

The correctness comes from maintaining a simple invariant: once called becomes true, it never changes back to false. Because closures preserve the variable across function calls, every invocation of the returned function sees the updated state. Therefore, fn executes exactly once, and all later calls return undefined.

Python Solution

Since this is a JavaScript function problem on LeetCode, the Python equivalent below demonstrates the same closure-based logic in Python syntax.

from typing import Callable, Any

class Solution:
    def once(self, fn: Callable[..., Any]) -> Callable[..., Any]:
        called = False

        def wrapper(*args, **kwargs):
            nonlocal called

            if called:
                return None  # Python equivalent of undefined

            called = True
            return fn(*args, **kwargs)

        return wrapper

The implementation starts by defining a boolean variable named called and setting it to False. This variable exists in the outer scope of the once method and acts as persistent state.

The nested wrapper function accepts arbitrary positional and keyword arguments using *args and **kwargs. This mirrors the flexibility of JavaScript functions and allows the wrapper to support any function signature.

Inside wrapper, the nonlocal keyword allows modification of the called variable defined in the enclosing scope. If called is already True, the function immediately returns None, which acts as the Python equivalent of JavaScript's undefined.

If the function has not been executed yet, the flag is updated to True, and fn is invoked with the provided arguments. The result of fn is returned directly.

Go Solution

Go does not support JavaScript-style variadic dynamic typing in the same way, but closures work similarly. We can use a boolean variable captured by the returned function.

package main

func once(fn func(...interface{}) interface{}) func(...interface{}) interface{} {
	called := false

	return func(args ...interface{}) interface{} {
		if called {
			return nil
		}

		called = true
		return fn(args...)
	}
}

The Go implementation uses a closure over the called variable. Because functions in Go can capture variables from their surrounding scope, the boolean state persists across invocations.

Instead of returning undefined, Go returns nil, which is the closest equivalent. The function accepts ...interface{} so that arbitrary arguments can be passed, similar to JavaScript's flexible parameter system.

Worked Examples

Example 1

Input:

fn = (a,b,c) => (a + b + c)
calls = [[1,2,3],[2,3,6]]

Initially:

Variable Value
called false

First Call: onceFn(1, 2, 3)

Step Action called Result
1 Check flag false Continue
2 Set called = true true Continue
3 Execute fn(1,2,3) true 6

Returned value: 6

Second Call: onceFn(2, 3, 6)

Step Action called Result
1 Check flag true Return undefined

Final output:

[{"calls":1,"value":6}]

Example 2

Input:

fn = (a,b,c) => (a * b * c)
calls = [[5,7,4],[2,3,6],[4,6,8]]

Initially:

Variable Value
called false

First Call: onceFn(5, 7, 4)

Step Action called Result
1 Check flag false Continue
2 Set called = true true Continue
3 Execute fn(5,7,4) true 140

Returned value: 140

Second Call: onceFn(2, 3, 6)

Step Action called Result
1 Check flag true Return undefined

Third Call: onceFn(4, 6, 8)

Step Action called Result
1 Check flag true Return undefined

Final output:

[{"calls":1,"value":140}]

Complexity Analysis

Measure Complexity Explanation
Time O(1) Each invocation only checks a boolean and possibly executes fn
Space O(1) Only one boolean variable is stored

The time complexity is constant because every function call performs a simple boolean check and possibly one function invocation. No loops or additional data structures are used. The space complexity is also constant because the implementation only stores a single boolean flag.

Test Cases

from typing import Callable, Any

class Solution:
    def once(self, fn: Callable[..., Any]) -> Callable[..., Any]:
        called = False

        def wrapper(*args, **kwargs):
            nonlocal called

            if called:
                return None

            called = True
            return fn(*args, **kwargs)

        return wrapper

solution = Solution()

# Example 1: addition function
once_fn = solution.once(lambda a, b, c: a + b + c)
assert once_fn(1, 2, 3) == 6  # first call executes function
assert once_fn(2, 3, 6) is None  # second call blocked

# Example 2: multiplication function
once_fn = solution.once(lambda a, b, c: a * b * c)
assert once_fn(5, 7, 4) == 140  # first call executes
assert once_fn(2, 3, 6) is None  # second call blocked
assert once_fn(4, 6, 8) is None  # third call blocked

# Single call only
once_fn = solution.once(lambda x: x * 2)
assert once_fn(5) == 10  # first and only call

# Function returning zero
once_fn = solution.once(lambda: 0)
assert once_fn() == 0  # valid falsy return value
assert once_fn() is None  # second call blocked

# Function returning False
once_fn = solution.once(lambda: False)
assert once_fn() is False  # valid boolean result
assert once_fn() is None  # second call blocked

# No argument function
once_fn = solution.once(lambda: 42)
assert once_fn() == 42  # works without parameters
assert once_fn() is None  # blocked afterward

# Different arguments on later calls
once_fn = solution.once(lambda a, b: a + b)
assert once_fn(10, 20) == 30  # first call succeeds
assert once_fn(999, 999) is None  # arguments ignored
Test Why
Addition example Validates the first provided example
Multiplication example Validates repeated blocking behavior
Single call Ensures correct behavior when only invoked once
Function returning 0 Verifies falsy values are handled correctly
Function returning False Ensures boolean return values do not break logic
No argument function Confirms flexible function signature support
Different later arguments Verifies later arguments are ignored

Edge Cases

One important edge case is when the original function returns a falsy value such as 0, False, or None. A naive implementation might mistakenly use the returned value to determine whether the function has already been called. For example, checking whether a stored result exists would fail for 0 or False. This implementation avoids that issue entirely by tracking execution using a dedicated boolean flag.

Another important case occurs when the wrapped function is only called once. Some implementations accidentally assume multiple invocations and may mishandle initialization or state transitions. In this implementation, the first call simply executes normally and updates the flag, so a single invocation works without special handling.

A third edge case is when later calls use completely different arguments. Since the problem guarantees the function may only execute once, later arguments must be ignored entirely. The implementation checks the boolean flag before attempting any execution, ensuring that subsequent inputs have no effect.

Finally, functions with varying numbers of arguments must be handled correctly. Some functions may take zero parameters while others may take many. By using *args and **kwargs in Python, and variadic arguments in Go, the implementation supports any function signature without modification.