Track your progress, run tests, and get AI feedback
Run code and see test results
Submit and get AI-powered feedback
Track which problems you've solved
Implement Debounce: Rate-Limiting Function Execution
In front-end development, handling frequent events like user input in a search bar or window resizing can lead to excessive function calls, which often impacts performance. The debounce technique is a crucial optimization strategy that ensures a function is only executed after a specified period of inactivity following its last invocation. This problem challenges you to implement a flexible debounce utility function that limits how often a given function can run, improving application responsiveness and efficiency.
Implement a debounce function that takes another function func, a delay in milliseconds, and an optional immediate boolean flag. The returned debounced function should invoke func only after delay milliseconds have passed since its last invocation. If immediate is true, func should be invoked on the leading edge of the timeout (i.e., immediately on the first call within a delay period), but not again until delay milliseconds have passed.
Your solution should implement the following function signature:
function debounce<T extends (...args: any[]) => any>(
func: T,
delay: number,
immediate?: boolean
): T;
func: The function to debounce. This function might take arguments and have a this context.delay: The number of milliseconds to wait after the last invocation before calling func.immediate: An optional boolean flag. If true, func is invoked on the leading edge of the timeout (immediately), otherwise on the trailing edge (after delay). Defaults to false.func with the specified rate-limiting behavior.delay is 0?
A: The function should still behave as debounced, effectively executing on the next microtask/macrotask tick, ensuring that multiple synchronous calls within the same execution stack only trigger one actual func invocation (on the trailing edge).this context and arguments be handled?
A: The debounced function must correctly preserve the this context and pass all received arguments to the original func when it is finally invoked.func during its last successful invocation. If func hasn't been invoked yet (e.g., in a trailing-edge debounce before the delay expires), it can return undefined.immediate is true and the debounced function is called repeatedly?
A: The func will execute immediately on the first call within a delay period. Subsequent calls within that same delay period will be ignored. After delay has passed, the next call will again execute immediately, starting a new delay period.Examples
Example 1
Input:
const log = (msg) => console.log(msg); const debouncedLog = debounce(log, 100); debouncedLog('A'); setTimeout(() => debouncedLog('B'), 50); setTimeout(() => debouncedLog('C'), 80); // Test trailing edge behavior setTimeout(() => { console.log('--- 200ms from start ---'); debouncedLog('D'); }, 200); setTimeout(() => debouncedLog('E'), 230);
Output:
C --- 200ms from start --- E
Note:
Calls 'A', 'B', 'C' occur within a 100ms window. Only the last call ('C') executes after 100ms of inactivity. After 200ms, a new debounce window starts. 'D' and 'E' are called, with 'E' being the last, executing 100ms after 'E' was called.
Example 2
Input:
const logImmediate = (msg) => console.log(msg); const debouncedImmediateLog = debounce(logImmediate, 100, true); debouncedImmediateLog('X'); setTimeout(() => debouncedImmediateLog('Y'), 50); setTimeout(() => debouncedImmediateLog('Z'), 80); // Test leading edge behavior after delay setTimeout(() => { console.log('--- 200ms from start ---'); debouncedImmediateLog('P'); }, 200); setTimeout(() => debouncedImmediateLog('Q'), 230);
Output:
X --- 200ms from start --- P
Note:
With immediate: true, 'X' executes immediately. 'Y' and 'Z' are ignored as they occur within the 100ms debounce period. After 200ms (100ms past the initial debounce window), a new period starts, and 'P' executes immediately. 'Q' is ignored.
Example 3
Input:
let sharedCount = 0; function increment(val) { this.sharedCount += val; return this.sharedCount; } const obj = { sharedCount: 0, increment: debounce(increment, 50) }; const obj2 = { sharedCount: 100, increment: debounce(increment, 50) };
obj.increment(1); setTimeout(() => obj.increment(2), 20); setTimeout(() => obj.increment(3), 40);
setTimeout(() => {
console.log(Obj 1 count after first window: ${obj.sharedCount});
obj2.increment(5);
setTimeout(() => obj2.increment(10), 20);
}, 100);
setTimeout(() => {
console.log(Obj 2 count after second window: ${obj2.sharedCount});
}, 200);
Output:
Obj 1 count after first window: 3 Obj 2 count after second window: 110
Note:
The this context and arguments are correctly passed. For obj, increment(3) is the last call within the first 50ms window, updating obj.sharedCount to 3. For obj2, increment(10) is the last call within its 50ms window, updating obj2.sharedCount to 110. Each debounced function maintains its own timer and context.
Constraints
func will always be a valid JavaScript function.delay will be a non-negative integer.this contexts.func.throttle function (which limits execution rate to a fixed interval, regardless of inactivity).this context and passes arguments correctly to the original function.setTimeout calls).