Two ways to rate-limit a function that fires too often (typing, scrolling, resizing): debounce waits for quiet, throttle runs at most once per interval.
Debounce
function debounce(fn, wait) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), wait);
};
}
const search = debounce((q) => console.log("searching for:", q), 30);
search("j");
search("ja");
search("jav"); // only this one survives the burst
setTimeout(() => search("javascript"), 60);searching for: jav
searching for: javascriptThrottle
function throttle(fn, wait) {
let last = 0, timer = null, pendingArgs = null;
return function (...args) {
const now = Date.now();
const remaining = wait - (now - last);
if (remaining <= 0) { // leading call
last = now;
fn.apply(this, args);
} else { // remember the latest call for the trailing edge
pendingArgs = args;
timer ??= setTimeout(() => {
last = Date.now();
timer = null;
fn.apply(this, pendingArgs);
}, remaining);
}
};
}
const onScroll = throttle((y) => console.log("handle scroll at", y), 50);
onScroll(0); // runs immediately (leading edge)
onScroll(10); // ignored…
onScroll(20); // …but the latest is kept for the trailing edgehandle scroll at 0
handle scroll at 20| Debounce | Throttle | |
|---|---|---|
| Runs | Once, after the burst ends | Regularly during the burst |
| Guarantee | Latest value, eventually | At most one call per wait |
| Typical use | Search box, validation, auto-save | Scroll, resize, drag, analytics |
Debounce vs throttle?