Given a function fn
, an array of arguments args
, and a timeout t
in milliseconds, return a cancel function cancelFn
. After a delay of cancelTimeMs
, the returned cancel function cancelFn
will be invoked, using setTimeout(cancelFn, cancelTimeMs)
. Initially, the execution of the function fn
should be delayed by t
milliseconds. If, before the delay of t
milliseconds, the function cancelFn
is invoked, it should cancel the delayed execution of fn
. Otherwise, if cancelFn
is not invoked within the specified delay t
, fn
should be executed with the provided args
as arguments.
Example 1:
Input: fn = (x) => x * 5, args = [2], t = 20
cancelTimeMs = 50;
Output: [{ "time": 20, "returned": 10 }]
Explanation:
const cancelFn = cancellable((x) => x * 5, [2], 20);
setTimeout(cancelFn, cancelTimeMs);
The cancellation was scheduled to occur after a delay of cancelTimeMs (50ms), which happened after the execution of fn(2) at 20ms.
Example 2:
Input: fn = (x) => x**2, args = [2], t = 100
cancelTimeMs = 50;
Output: []
Explanation:
const cancelFn = cancellable((x) => x**2, [2], 100);
setTimeout(cancelFn, cancelTimeMs);
The cancellation was scheduled to occur after a delay of cancelTimeMs (50ms), which happened before the execution of fn(2) at 100ms, resulting in fn(2) never being called.
Write a function cancellable
in JavaScript that implements this behavior.
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The brute force approach to timeout cancellation involves meticulously tracking every timeout that is set. When a cancellation is requested, we go through the entire list of timeouts to find the matching one. If found, we stop that specific timeout from running.
Here's how the algorithm would work step-by-step:
import time
class TimeoutManager:
def __init__(self):
self.active_timeouts = []
def set_timeout(self, function_to_run, delay_seconds):
timeout_entry = {
'function': function_to_run,
'delay': delay_seconds,
'scheduled_time': time.time() + delay_seconds,
'cancelled': False
}
self.active_timeouts.append(timeout_entry)
return timeout_entry
def cancel_timeout(self, timeout_entry):
# Iterate through all timeouts to find the one to cancel.
for existing_timeout in self.active_timeouts:
if existing_timeout == timeout_entry:
# Mark the timeout as cancelled to prevent execution.
existing_timeout['cancelled'] = True
return
def run_pending_timeouts(self):
current_time = time.time()
timeouts_to_remove = []
for timeout_entry in self.active_timeouts:
if timeout_entry['scheduled_time'] <= current_time and not timeout_entry['cancelled']:
# This timeout is due and hasn't been cancelled, run it
timeout_entry['function']()
timeouts_to_remove.append(timeout_entry)
# Clean up executed timeouts.
for timeout_entry in timeouts_to_remove:
self.active_timeouts.remove(timeout_entry)
if __name__ == '__main__':
def my_function():
print("Function executed after timeout!")
manager = TimeoutManager()
timeout1 = manager.set_timeout(my_function, 2)
timeout2 = manager.set_timeout(lambda: print("Another timeout"), 5)
time.sleep(1)
# Cancel the first timeout.
manager.cancel_timeout(timeout1)
time.sleep(6)
# Execute all pending timeouts to demonstrate the cancellation
manager.run_pending_timeouts()
The goal is to create a system that starts a task and allows you to stop it before it finishes if it takes too long. We will use a timer to keep track of how long the task has been running and a mechanism to signal that the task should stop.
Here's how the algorithm would work step-by-step:
import time
import threading
class TimeoutCancellation:
def __init__(self, timeout_duration):
self.timeout_duration = timeout_duration
self.task_thread = None
self.stop_event = threading.Event()
def start_task(self, task):
self.stop_event.clear()
self.task_thread = threading.Thread(target=self._run_with_timeout, args=(task,))
self.task_thread.start()
def _run_with_timeout(self, task):
timer = threading.Timer(self.timeout_duration, self._signal_timeout)
timer.start()
try:
task(self.stop_event)
timer.cancel()
except Exception as exception:
print(f"Task raised an exception: {exception}")
finally:
timer.cancel()
def _signal_timeout(self):
# Set the stop event to signal the task to stop.
self.stop_event.set()
class MyTask:
def __call__(self, stop_event):
# Simulate a task that might take a long time.
for i in range(10):
if stop_event.is_set():
print("Task cancelled.")
return
print(f"Task running: {i}")
time.sleep(1)
print("Task completed.")
if __name__ == '__main__':
timeout_duration_seconds = 3
timeout_cancellation = TimeoutCancellation(timeout_duration_seconds)
my_task = MyTask()
print("Starting task...")
timeout_cancellation.start_task(my_task)
timeout_cancellation.task_thread.join()
print("Task finished.")
Case | How to Handle |
---|---|
Timeout duration is zero or negative | Treat zero as execute immediately and negative as invalid, throw an error, or use a default positive timeout. |
Callback function is null or undefined | Throw an IllegalArgumentException or TypeError immediately to prevent a NullPointerException later. |
Calling clearTimeout with an invalid or already cleared timeout ID | Ignore the clearTimeout call or handle with grace by adding a simple check if the timeoutID exists, since this is a no-op. |
Multiple timeouts created with the same callback | Each timeout needs a separate, unique ID and to be managed independently. |
Timeout firing while the callback is already executing (re-entrancy) | Avoid re-entrancy by disabling or ignoring future timeout events until the callback completes. |
System clock changes during timeout execution | Use a monotonic clock if available; otherwise be aware that time drift might cause unexpected timing. |
Integer overflow when calculating the timeout expiration time | Use a data type capable of holding large time values, like long, and check for overflow during addition operations. |
The process is paused or suspended during the timeout. | The timeout might not trigger when expected, consider using system calls that are robust against suspension and resume behavior. |