Debounce vs Throttle: Differences, Use Cases, and Implementations
Interview-ready guide to debounce vs throttle: how they work, when to use each, and clean JavaScript implementations (with leading/trailing options).
On this page
Debounce and throttle are common interview questions because they show whether you understand:
- performance in the browser
- event handling
- timing control
They’re also extremely practical.
30‑second interview answer
Debounce delays execution until events stop firing for X ms (great for search input). Throttle runs at most once every X ms (great for scroll/resize). Debounce reduces calls by waiting for a pause; throttle reduces calls by enforcing a max rate.
Key points
- Debounce = “wait for quiet”.
- Throttle = “limit the rate”.
- Decide based on whether you want the final value or continuous updates.
1) The difference (one-liner)
- Debounce: “Only run after the event stops firing for X ms.”
- Throttle: “Run at most once every X ms.”
2) When to use Debounce
Use debounce when you want the final result after the user pauses:
- search input (typeahead)
- resizing a window (final layout calc)
- autosave after typing stops
Example
input.addEventListener('input', debounce((e) => {
search(e.target.value);
}, 300));3) When to use Throttle
Use throttle when you want updates during continuous activity, but not too frequently:
- scroll events (infinite scroll)
- mousemove (dragging)
- analytics pings
Example
window.addEventListener('scroll', throttle(() => {
trackScroll();
}, 200));4) Debounce implementation (simple)
export function debounce(fn, delay = 300) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}Interview notes
- we clear the previous timer
- only the last call survives
thisand args are preserved viaapply
5) Throttle implementation (simple)
export function throttle(fn, interval = 200) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}This is a "leading" throttle (runs immediately on first trigger).
6) More complete versions (leading/trailing)
Interviewers sometimes ask for options.
Debounce with leading option
export function debounceAdvanced(fn, delay = 300, { leading = false } = {}) {
let timer;
let invoked = false;
return function (...args) {
if (leading && !invoked) {
fn.apply(this, args);
invoked = true;
}
clearTimeout(timer);
timer = setTimeout(() => {
invoked = false;
if (!leading) fn.apply(this, args);
}, delay);
};
}Throttle with trailing call
export function throttleAdvanced(fn, interval = 200) {
let lastTime = 0;
let timer;
let lastArgs;
let lastThis;
return function (...args) {
const now = Date.now();
lastArgs = args;
lastThis = this;
const remaining = interval - (now - lastTime);
if (remaining <= 0) {
clearTimeout(timer);
timer = undefined;
lastTime = now;
fn.apply(lastThis, lastArgs);
return;
}
// schedule a trailing call
if (!timer) {
timer = setTimeout(() => {
lastTime = Date.now();
timer = undefined;
fn.apply(lastThis, lastArgs);
}, remaining);
}
};
}7) Production-grade Debounce: Adding .cancel() and .flush()
In real-world frontends (and Senior/Staff interviews), debounced functions must support cleanup on component unmount and immediate execution when submitting forms.
export function debounceWithControls(fn, delay = 300) {
let timerId = null;
let lastArgs = null;
let lastThis = null;
function debounced(...args) {
lastArgs = args;
lastThis = this;
clearTimeout(timerId);
timerId = setTimeout(() => {
fn.apply(lastThis, lastArgs);
timerId = null;
lastArgs = null;
lastThis = null;
}, delay);
}
// Cancel any scheduled execution (crucial for React useEffect cleanup)
debounced.cancel = function () {
if (timerId !== null) {
clearTimeout(timerId);
timerId = null;
lastArgs = null;
lastThis = null;
}
};
// Immediately execute the pending call without waiting for timer
debounced.flush = function () {
if (timerId !== null) {
clearTimeout(timerId);
fn.apply(lastThis, lastArgs);
timerId = null;
lastArgs = null;
lastThis = null;
}
};
return debounced;
}React usage with cleanup
useEffect(() => {
const debouncedSearch = debounceWithControls((query) => {
fetchResults(query);
}, 300);
// Cancel pending timer if user unmounts before 300ms expires
return () => debouncedSearch.cancel();
}, []);8) Throttle via requestAnimationFrame (RAF Throttle)
For visual DOM operations (scroll positions, cursor trailers, resize calculations), JavaScript timers (setTimeout) do not sync with the browser monitor refresh rate (60Hz / 120Hz). Throttling with requestAnimationFrame coordinates execution directly with the browser render pipeline, preventing layout thrashing and dropped frames.
export function throttleRAF(fn) {
let queued = false;
let lastArgs = null;
return function (...args) {
lastArgs = args;
if (queued) return;
queued = true;
requestAnimationFrame(() => {
queued = false;
fn.apply(this, lastArgs);
});
};
}Interview note: requestAnimationFrame automatically pauses when the browser tab is in the background, conserving battery and CPU cycles.
9) Real Interview Puzzle: Predict the Timing Tick
Question: Consider the following calls to a debounced and throttled log function with an interval of 100ms. At what timestamps do the executions actually occur, and with what values?
const logDebounce = debounce((v) => console.log('Debounce:', v), 100);
const logThrottle = throttle((v) => console.log('Throttle:', v), 100);
// Timeline:
// t = 0ms: call(1)
// t = 50ms: call(2)
// t = 160ms: call(3)Step-by-Step Execution Trace
Standard Trailing Debounce (delay = 100ms)
t = 0ms:call(1)received. Timer scheduled fort = 100ms.t = 50ms:call(2)received. Previous timer cancelled! New timer scheduled fort = 50 + 100 = 150ms.t = 150ms: Timer fires. Output:Debounce: 2.t = 160ms:call(3)received. Timer scheduled fort = 160 + 100 = 260ms.t = 260ms: Timer fires. Output:Debounce: 3.- Final outputs:
Debounce: 2at 150ms,Debounce: 3at 260ms.call(1)was completely debounced away.
Standard Leading Throttle (interval = 100ms)
t = 0ms:call(1)received. Since0 - 0 >= 100(orlastTime == 0), runs immediately. Output:Throttle: 1.lastTime = 0ms. Next eligible run is att >= 100ms.t = 50ms:call(2)received. Elapsed time50 - 0 = 50ms < 100ms. Call dropped (simple leading throttle).t = 160ms:call(3)received. Elapsed time160 - 0 = 160ms >= 100ms. Runs immediately. Output:Throttle: 3.- Final outputs:
Throttle: 1at 0ms,Throttle: 3at 160ms.call(2)was suppressed.
10) Common interview Q&A
Q: Is debounce or throttle better for search input?
- Debounce. You typically want the final value after the user pauses typing.
Q: What about a scroll or window resize handler?
- Throttle. You want periodic updates while scrolling to provide smooth UI feedback. If visual updates only, prefer
throttleRAF.
Q: Can you debounce a promise-returning function?
- Yes, but you should handle potential race conditions. If an earlier asynchronous request finishes after a later one, use
AbortControllerto abort the obsolete request.
Summary checklist
- I can define debounce (wait for quiet) vs throttle (rate limiting).
- I can choose between them based on whether intermediate values matter.
- I can implement standard debounce and throttle from scratch in an interview.
- I know how to add
.cancel()and.flush()methods for lifecycle safety. - I can use
requestAnimationFramethrottle for screen-synchronized rendering.
Authoritative References & Standards
- MDN Web Docs: window.requestAnimationFrame
- W3C Performance Timeline Specification
- Lodash debounce & throttle implementation specifications
Written by Rajesh Dhiman at Eunix Tech for the Frontend Interview preparation track.
Looking for structured, offline interview prep?
Free online lessons • Portable Amazon Kindle / Paperback
JavaScript Interview Guide
By Rajesh Dhiman
Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.
Frontend Interview Guide (2025 Edition)
By Rajesh Dhiman
Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.
Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.
Written by Rajesh Dhiman
Authored by Rajesh Dhiman and engineered at Eunix Tech. Focused on deep-dive frontend engineering, JavaScript runtime internals, React architecture, and web performance.