Search boxes that fire a request on every keystroke and scroll handlers that run 100 times a second are classic performance bugs. Two tiny functions fix them.
Debounce: wait until the user stops
function debounce(fn, delay = 300) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
Each call resets the timer. fn only runs once calls stop for delay ms.
Throttle: run at most once per interval
function throttle(fn, interval = 200) {
let last = 0;
let trailing;
return function (...args) {
const now = Date.now();
const remaining = interval - (now - last);
clearTimeout(trailing);
if (remaining <= 0) {
last = now;
fn.apply(this, args);
} else {
// make sure the final call isn't lost
trailing = setTimeout(() => {
last = Date.now();
fn.apply(this, args);
}, remaining);
}
};
}
Try it in a page
<input id="search" placeholder="Type...">
<p id="out"></p>
<p id="scroll"></p>
<div style="height:3000px"></div>
<script>
/* paste debounce and throttle here */
const out = document.getElementById("out");
document.getElementById("search").addEventListener(
"input",
debounce(e => { out.textContent = "Searching for: " + e.target.value; }, 400)
);
const s = document.getElementById("scroll");
window.addEventListener(
"scroll",
throttle(() => { s.textContent = "scrollY = " + Math.round(scrollY); }, 250)
);
</script>
Test it in Node
const log = debounce(x => console.log("debounced:", x), 100);
log(1); log(2); log(3); // prints only "debounced: 3"
const tick = throttle(x => console.log("throttled:", x), 100);
let i = 0;
const id = setInterval(() => { tick(++i); if (i === 10) clearInterval(id); }, 30);
// prints roughly every 100ms, plus the final value
Which one?
| Situation | Use |
|---|---|
| Search-as-you-type, autosave, window resize end | debounce |
| Scroll position, mouse move, infinite scroll check | throttle |
Rule of thumb: debounce cares about the last event, throttle cares about a steady rhythm.
Top comments (0)