Debounce and Throttle
Two ways to tame a rapid-fire event: wait for quiet before reacting, or react on a steady schedule no matter how fast events arrive.
Loading visual…
Definition
Debounce and throttle both wrap a function so it runs less often than the event that triggers it, but they make different promises. A debounced function waits for a pause: every new call resets a timer, and the wrapped function only actually runs once that timer finishes without being reset again, meaning it fires once, after the burst of calls has gone quiet. A throttled function makes a different promise: it runs at most once per fixed interval, no matter how many times it is called during that interval, so it keeps firing at a steady cadence throughout a burst instead of waiting for it to end. Both are typically built with closures over a timer id (or a last-run timestamp) that a rapid stream of calls shares, which is why they are usually implemented as higher-order functions: a function that takes your handler and returns a new, rate-limited version of it. Debounce suits input you only care about once it settles, like a search box waiting for someone to stop typing. Throttle suits input you want to keep responding to steadily even while it keeps happening, like a scroll or resize handler.
Examples
function debounce(fn, wait) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), wait);
};
}
const onSearch = debounce((q) => runSearch(q), 300);
// typing "cat" fast only calls runSearch once, 300ms after the last keystrokeEvery call clears the previous timer and starts a new one, so only the last call in a burst ever survives long enough to actually run fn.
function throttle(fn, wait) {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last >= wait) {
last = now;
fn(...args);
}
};
}
const onScroll = throttle(() => updatePosition(), 200);
// scrolling continuously still calls updatePosition about every 200msEach call checks how long it has been since the last run; only once wait milliseconds have passed does it run fn again and reset the clock.
Common mistakes
- Reaching for throttle when you only care about the final state; debounce (fire once, after things settle) is usually the better fit.
- Reaching for debounce on a scroll or drag handler, which then never fires at all while the user keeps moving continuously.
- Forgetting to clear the pending timer when a debounced function's owning component unmounts, letting a stale call fire later.
Key takeaways
- Debounce fires once, only after calls stop arriving for the configured wait.
- Throttle fires repeatedly, at most once per fixed interval, throughout a burst.
- Both are commonly built as a closure over shared timer or timestamp state, wrapping the original function.
- Choosing between them depends on whether you want the final result only, or steady updates throughout.
A user scrolls continuously for 2 seconds. A handler is throttled to run at most once every 500ms. Roughly how many times does the handler run?
Where you see this
- Debouncing a search input so a network request only fires after the user stops typing.
- Throttling a window scroll or resize listener so layout calculations run on a steady cadence instead of on every pixel of movement.
- Debouncing a save-as-you-type feature so it writes once the user pauses, instead of on every keystroke.
- Throttling a drag handler so position updates happen at a smooth, bounded rate instead of hundreds of times per second.