Debounce and Throttle
Debounce delays work until input stops; throttle caps how often work runs. They solve different burst patterns and are not interchangeable.
Definition
Debounce postpones a function until a quiet period has elapsed since the last call, collapsing a burst of events into one invocation. Throttle guarantees a maximum invocation rate by running at most once per interval, regardless of how many calls arrive. Both reduce work caused by high-frequency events, but debounce optimizes for the final state while throttle optimizes for steady progress.
Context and scope
Use debounce for search input, form validation, and resize handlers where only the settled value matters. Use throttle for scroll position, pointer movement, and progress reporting where periodic updates are required. Choosing the wrong one either drops updates users expect or delays the only update they need.
Common misconceptions
- Debounce and throttle are interchangeable — debounce waits for silence, throttle emits at a bounded rate
- Debounce always delays execution — leading-edge debounce runs immediately and suppresses the trailing call
- Throttle drops every call in between — implementations may queue a trailing call so the last value is not lost
Anti-patterns
- Debouncing a scroll handler that must update a sticky header continuously
- Throttling a search box without a trailing call, so the final query never runs
- Recreating the debounced function on every render, which resets its timer and defeats the purpose
Related concepts
Related examples
Authoritative source
What each one guarantees
Debounce answers “run this once the caller has stopped calling.” Each new call resets the timer. A search box that fires on every keystroke should query once after the user pauses, not once per character.
Throttle answers “run this at most once per interval.” A scroll handler that reads scrollY can run every 100 ms even while the user scrolls continuously.
function debounce(fn, wait) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), wait);
};
}
function throttle(fn, interval) {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last >= interval) {
last = now;
fn(...args);
}
};
}
When to use which
Use debounce when intermediate values have no value: search suggestions, autosave, validation after typing. Use throttle when intermediate values matter: scroll-linked animation, analytics sampling, drag coordinates.
Compared to a rate limiter, both are client-side ergonomics controls. A server-side rate limiter protects shared capacity; debounce and throttle protect the browser and downstream APIs from unnecessary work.
Pitfalls
The most common bug is recreating the wrapper inside a render or event registration, which resets the timer on every call. Create it once and reuse it. The second is forgetting the trailing call: a pure leading-edge throttle can drop the final value, which is often the one the user cares about.