JavaScript
medium
very high
mid
How would you implement debounce and throttle, and when do you use each?
Debounce delays the call until activity stops; throttle caps how often the call can fire. Both control noisy events but solve different problems.
6 min read·~15 min to think through
Debounce — fires once after the input has been quiet for N ms. Use for: search-as-you-type, autosave on form input, window-resize end. The user sees no work happen until they pause.
Throttle — fires at most once per N ms while activity continues. Use for: scroll handlers, mouse-move tracking, drag, analytics. The user sees regular updates while they keep going.
Mental model: debounce waits for silence, throttle paces the signal.
Implementation traps to mention:
leadingvstrailingedge — does the first call fire immediately or only after the wait?- Cancellation — return a
.cancel()so React effects can clean up. - Preserving
thisandargumentsof the call.
Code
ts
function debounce<T extends (...a: any[]) => any>(fn: T, wait = 200) {
let t: ReturnType<typeof setTimeout> | null = null;
function debounced(this: any, ...args: Parameters<T>) {
if (t) clearTimeout(t);
t = setTimeout(() => fn.apply(this, args), wait);
}
debounced.cancel = () => { if (t) { clearTimeout(t); t = null; } };
return debounced as T & { cancel: () => void };
}ts
function throttle<T extends (...a: any[]) => any>(fn: T, wait = 200) {
let last = 0, t: ReturnType<typeof setTimeout> | null = null, lastArgs: any[] = [];
return function (this: any, ...args: Parameters<T>) {
const now = Date.now();
const remaining = wait - (now - last);
lastArgs = args;
if (remaining <= 0) {
if (t) { clearTimeout(t); t = null; }
last = now;
fn.apply(this, args);
} else if (!t) {
t = setTimeout(() => {
last = Date.now();
t = null;
fn.apply(this, lastArgs);
}, remaining);
}
};
}Follow-up questions
- •When would you use both debounce and a maxWait together?
- •How does requestAnimationFrame compare to a 16ms throttle for scroll handlers?
- •How do you correctly debounce inside a React component (closure pitfalls)?
Common mistakes
- •Calling debounce/throttle inside render — every render creates a new instance, so the timer never accumulates.
- •Forgetting to cancel on unmount — the timer fires after the component is gone and crashes setState.
- •Confusing the two: using debounce for scroll (jumps after pause) or throttle for search (extra requests).
Performance considerations
- •For 60fps UI work tied to events, prefer `requestAnimationFrame` over a 16ms throttle — it aligns with frames and pauses on hidden tabs.
- •Throttling DOM-touching handlers prevents layout thrashing; debouncing them delays the cost.
Edge cases
- •Page-hidden tabs (`visibilitychange`) freeze setTimeout to a coarse cadence — throttle accuracy suffers.
- •Trailing-edge debounce on unmount can fire after the component is gone if you don't cancel.
Real-world examples
- •Search-as-you-type with a 300ms debounce avoids one API call per keystroke.
- •Infinite-scroll position checks throttled to 100ms keep CPU calm while preserving smoothness.
Senior engineer discussion
At senior level discuss leading+trailing semantics, maxWait (lodash), and how you'd test these (fake timers + flush).
Related questions
Performance
Medium
hot
9 min