Debounce and throttle both limit how often a function runs, which is why they get mixed up. The difference is simple once you see it: debounce waits for quiet; throttle enforces a steady rate.
The one-sentence difference
- Debounce: run only AFTER the events stop for X ms. (Wait until the user finishes.)
- Throttle: run at most once every X ms while events keep firing. (Steady drip.)
Debounce — for 'when they're done'
Search-as-you-type: don't hit the API on every keystroke, only once typing pauses.
function debounce<T extends (...a: any[]) => void>(fn: T, ms: number) {
let t: ReturnType<typeof setTimeout>;
return (...args: Parameters<T>) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), ms);
};
}
const search = debounce((q: string) => api.search(q), 300);
input.addEventListener("input", (e) => search(e.target.value));Throttle — for 'at a steady rate'
Scroll or resize handlers: run periodically as it fires, not hundreds of times a second.
function throttle<T extends (...a: any[]) => void>(fn: T, ms: number) {
let last = 0;
return (...args: Parameters<T>) => {
const now = Date.now();
if (now - last >= ms) {
last = now;
fn(...args);
}
};
}
const onScroll = throttle(() => updateScrollProgress(), 100);
window.addEventListener("scroll", onScroll);Rule of thumb: debounce for 'the user finished' (search box, auto-save, resize-then-recalculate). Throttle for 'keep up but not too often' (scroll position, mousemove, firing analytics).
Debounce collapses a burst into one call after it settles; throttle caps the rate during the burst. Pick by asking whether you want the final result or a regular sample.