Debounce delays execution until a pause in events โ fire only after the user stops triggering events for X ms. Throttle limits rate โ fire at most once every X ms regardless of how fast events arrive.
A mnemonic: debounce = elevator door (keeps reopening as people walk in; only closes after no one enters for a moment). Throttle = shooting a machine gun (fires at a fixed rate no matter how fast you pull the trigger).
Debounce โ implementation
The key insight: every call clears the previous timer and starts a fresh one. Only when the timer actually fires does the wrapped function run.
console.log. Async logs (setTimeout/Promise) appear in real execution order.Real uses: search-box input (wait for user to stop typing before fetching), window resize handler, form auto-save.
Debounce with leading edge
Sometimes you want to fire immediately on the first call, then silence until the burst ends:
function debounce(fn, ms, { leading = false } = {}) {
let timerId;
let hasLeadFired = false;
return function (...args) {
if (leading && !timerId) {
fn.apply(this, args); // fire immediately on first call
hasLeadFired = true;
}
clearTimeout(timerId);
timerId = setTimeout(() => {
timerId = null;
hasLeadFired = false;
if (!leading) fn.apply(this, args);
}, ms);
};
}
Debounce with cancel
A .cancel() method is often asked for as a follow-up:
function debounce(fn, ms) {
let timerId;
function debounced(...args) {
clearTimeout(timerId);
timerId = setTimeout(() => fn.apply(this, args), ms);
}
debounced.cancel = () => clearTimeout(timerId);
return debounced;
}
Throttle โ implementation
Record when the function last ran. On each call, check if enough time has passed; if yes, run it and update the timestamp.
console.log. Async logs (setTimeout/Promise) appear in real execution order.Real uses: scroll event handlers, mouse-move tracking, game input loops, rate-limiting API calls.
Throttle with trailing call (timer variant)
The timestamp approach drops trailing calls. If you want the last call in a burst to always fire, combine a timestamp with a timer:
function throttle(fn, ms) {
let lastTime = 0;
let timerId;
return function (...args) {
const now = Date.now();
const remaining = ms - (now - lastTime);
clearTimeout(timerId);
if (remaining <= 0) {
lastTime = now;
fn.apply(this, args);
} else {
timerId = setTimeout(() => {
lastTime = Date.now();
fn.apply(this, args);
}, remaining);
}
};
}
When to reach for each
| Situation | Use |
|---|---|
| Search box โ fetch after user stops typing | Debounce |
| Window resize โ recalculate layout | Debounce |
| Scroll position tracking | Throttle |
| Mouse-move tooltip | Throttle |
| Button that must not double-fire | Debounce (leading) |
| Game loop / animation frame rate cap | Throttle |