Implement debounce(fn, delay)
Implement debounce(fn, delay) that returns a debounced wrapper: fn runs only delay ms after the LAST call, and any call within that window cancels the pending run and restarts the timer. Preserve this and forward all arguments to fn. Rapidly calling the wrapper 8 times runs fn once.
function debounce(fn, delay) {
// your code here
}
Write the implementation.
Keep a timeoutId in the closure. The returned function calls clearTimeout(timeoutId) to cancel any pending run, then timeoutId = setTimeout(...) to schedule fn after delay. Inside the timer call fn.apply(this, args) to preserve this and forward arguments. Only the last call's timer survives, so fn runs once after activity stops.
- ✗Confusing debounce with throttle — debounce waits for a quiet period, not a fixed rate
- ✗Forgetting
clearTimeout, so every call fires after its own delay instead of just the last - ✗Losing
this/arguments by callingfn()directly instead offn.apply(this, args)
- →How would you add a leading-edge option that fires on the first call too?
- →How does the closed-over
timeoutIdsurvive between separate calls?
Solution
Keep a timeoutId in the closure; on each call cancel the previous timer and schedule a new one.
function debounce(fn, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
How it works
timeoutId lives in the closure and survives across separate calls to the wrapper. On each call clearTimeout cancels the previously scheduled run, and setTimeout schedules a fresh timer for delay. As long as calls arrive faster than once per delay, the timer keeps resetting and fn never runs.
When the calls stop, the last timer survives to completion and runs fn exactly once. fn.apply(this, args) preserves the caller's this context and forwards all arguments. The arrow function inside setTimeout has no this of its own, so this is taken from the wrapper. </content>