Given a function fn
and a time in milliseconds t
, return a debounced version of that function.
A debounced function is a function whose execution is delayed by t
milliseconds and whose execution is cancelled if it is called again within that window of time. The debounced function should also receive the passed parameters.
For example, let's say t = 50ms
, and the function was called at 30ms
, 60ms
, and 100ms
.
The first 2 function calls would be cancelled, and the 3rd function call would be executed at 150ms
.
If instead t = 35ms
, The 1st call would be cancelled, the 2nd would be executed at 95ms
, and the 3rd would be executed at 135ms
.
The above diagram shows how debounce will transform events. Each rectangle represents 100ms and the debounce time is 400ms. Each color represents a different set of inputs.
Please solve it without using lodash's _.debounce()
function.
Example 1:
Input: t = 50 calls = [ {"t": 50, inputs: [1]}, {"t": 75, inputs: [2]} ] Output: [{"t": 125, inputs: [2]}] Explanation: let start = Date.now(); function log(...inputs) { console.log([Date.now() - start, inputs ]) } const dlog = debounce(log, 50); setTimeout(() => dlog(1), 50); setTimeout(() => dlog(2), 75); The 1st call is cancelled by the 2nd call because the 2nd call occurred before 100ms The 2nd call is delayed by 50ms and executed at 125ms. The inputs were (2).
Example 2:
Input: t = 20 calls = [ {"t": 50, inputs: [1]}, {"t": 100, inputs: [2]} ] Output: [{"t": 70, inputs: [1]}, {"t": 120, inputs: [2]}] Explanation: The 1st call is delayed until 70ms. The inputs were (1). The 2nd call is delayed until 120ms. The inputs were (2).
Example 3:
Input: t = 150 calls = [ {"t": 50, inputs: [1, 2]}, {"t": 300, inputs: [3, 4]}, {"t": 300, inputs: [5, 6]} ] Output: [{"t": 200, inputs: [1,2]}, {"t": 450, inputs: [5, 6]}] Explanation: The 1st call is delayed by 150ms and ran at 200ms. The inputs were (1, 2). The 2nd call is cancelled by the 3rd call The 3rd call is delayed by 150ms and ran at 450ms. The inputs were (5, 6).
Constraints:
0 <= t <= 1000
1 <= calls.length <= 10
0 <= calls[i].t <= 1000
0 <= calls[i].inputs.length <= 10
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:
Debouncing is about delaying a function call until after a certain amount of time has passed without any new calls. The brute force way is to simply keep track of every function call and, for each call, wait the specified time before executing it. This means handling each call individually and independently.
Here's how the algorithm would work step-by-step:
import time
import threading
def debounce_brute_force(function, delay):
timer = None
def debounced(*arguments, **keywords):
nonlocal timer
# If there's a pending timer, cancel it
if timer is not None:
timer.cancel()
# Define the function that will actually be executed
def delayed_execution():
function(*arguments, **keywords)
# Create a new timer
timer = threading.Timer(delay, delayed_execution)
# Starts the timer
timer.start()
# We don't return anything because we call the function asynchronously
return debounced
The goal of Debounce is to only execute a function after a certain amount of time has passed since the last time it was called. We'll use a timer to keep track of this waiting period, ensuring the function isn't called too frequently. The clever trick is to use a single timer that gets reset each time the function is invoked before its delay time has elapsed, this way we can avoid multiple calls to the function.
Here's how the algorithm would work step-by-step:
import threading
def debounce(function_to_debounce, delay):
timer = None
def debounced_function(*args, **kwargs):
nonlocal timer
# Clear existing timer to prevent premature execution
if timer is not None:
timer.cancel()
# Define the function to be executed after the delay
def run_function():
function_to_debounce(*args, **kwargs)
# Start a new timer
timer = threading.Timer(delay, run_function)
timer.start()
return debounced_function
Case | How to Handle |
---|---|
Null or undefined function input | Throw an error or return a no-op function to prevent unexpected behavior |
Zero or negative wait time | Treat a zero or negative wait time as an immediate execution or throw an error |
Function called rapidly, more frequently than the wait time | Ensure only the last function call within the wait period is executed by cancelling previous timers |
The debounced function throws an error | Propagate the error to the caller or provide a try-catch within the debounced function itself |
The 'this' context is different in each call | Preserve the correct 'this' context using Function.prototype.apply or Function.prototype.call |
Arguments to the function are different in each call | Capture and pass the correct arguments from the latest invocation when the debounced function runs. |
Debounced function is garbage collected before execution | No specific handling is needed; Javascript handles garbage collection automatically, but consider potential memory leaks from prolonged timers. |
Multiple debounced functions with overlapping calls | Each debounced function needs its own independent timer and state management to prevent interference. |