What you are looking at
Same events, two different questions about time.
The raw lane is every event as it happens. The other two lanes are the same events after a decision about when to actually act. Debounce asks "has it gone quiet yet?" and holds off until it has, then fires once. Throttle asks "has enough time passed since I last acted?" and fires on that clock, ignoring everything in between.
Neither is a speed setting. They answer different questions. Debounce is about the final state after a burst; throttle is about steady, rate-limited progress during it. The counts in the demo show how few calls each makes from the same stream.
Choosing between them
Pick by whether you care about the end of the burst or the whole of it.
The mistake is treating them as interchangeable. They produce different behaviour, and the right one depends entirely on what the action is for.
Reach for debounce
When only the final state matters
Search-as-you-type, autosave, field validation, re-running a sync after a record stops changing. You do not want to act on every intermediate keystroke or edit, only on where things land once they settle. Debounce waits for the quiet and fires once.
Reach for throttle
When you need progress during the burst
Scroll position, resize layout, a live progress readout, or any call to a downstream service with a rate limit. You want regular updates while events keep coming, and you must not exceed a fixed rate. Throttle fires on a clock and drops the rest.
function debounce(fn, wait) {
let timer;
return (...args) => {
clearTimeout(timer); // cancel the pending run
timer = setTimeout(() => fn(...args), wait); // fire only after `wait` of quiet
};
}
// search-as-you-type: one request after the user stops typing
input.addEventListener('input', debounce(search, 300));Each event clears the pending timer and sets a new one, so the function only runs once no event has arrived for the wait period. Thirty keystrokes become one request, sent when the typing stops.
function throttle(fn, interval) {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last >= interval) { // at most once per interval
last = now;
fn(...args);
}
};
}
// scroll: run the handler on a fixed clock, not on every frame
window.addEventListener('scroll', throttle(onScroll, 100));The wrapped function runs, then ignores every call until the interval has passed. A scroll that fires a hundred times a second becomes ten steady calls, enough to keep a UI in step without drowning the main thread.
The same idea, one level up
A trigger that fires on every change is the same bug as a handler that fires on every keystroke.
This is not only a front-end concern. Automations have the same problem: a record edited ten times, a webhook that arrives in floods. Debounce and throttle apply to triggers exactly as they do to events.
Where it shows up in automation
Rate-limiting a trigger
A record edited repeatedly should sync once the edits settle, not on every save, that is a debounced trigger.
A busy webhook that would blow a downstream rate limit should be throttled to a safe, steady rate.
A notification that could fire on every status change is better debounced, so a human gets one update, not twenty.
The window is a business decision: how stale is acceptable, and how fast can the downstream system actually keep up.
// A record can change ten times in a minute as someone edits it.
// Debounce the trigger so the workflow runs once, after the edits settle.
const syncRecord = debounce(syncToCrm, 5 * 60 * 1000); // 5 minutes of quiet
onRecordChange(syncRecord);
// A busy webhook can fire hundreds of times an hour.
// Throttle the downstream call so it never exceeds the rate limit.
const notify = throttle(postToSlack, 60 * 1000); // at most once a minute
onWebhook(notify);The window is now measured in minutes instead of milliseconds, but the shape is identical. Debounce the trigger when you want the settled result; throttle it when you must respect what the downstream system can take.
The rule of thumb
Debounce waits for quiet; throttle fires on a clock. Pick by whether you care about the end or the whole burst.
If only the final state matters, debounce and act once when the burst settles. If you need steady progress or must respect a rate limit, throttle and act on a fixed clock. The same choice applies whether the burst is keystrokes or record changes.
