Debounce and Throttle in JavaScript
Some events fire very often: typing, scrolling, resizing. Running expensive work (an API call, a layout calculation) on every event wastes resources and can make the page janky.
Debounce: wait until things calm down
Run the function only after the events have stopped for a given time. Perfect for search-as-you-type and autosave.
function debounce(fn, wait = 300) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), wait);
};
}
const search = debounce(async (query) => {
const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
renderResults(await res.json());
}, 300);
input.addEventListener('input', (e) => search(e.target.value));
Throttle: at most once per interval
Run the function at a steady maximum rate while events keep coming. Good for scroll position tracking and drag handlers.
function throttle(fn, interval = 100) {
let last = 0;
let timer;
return function (...args) {
const now = Date.now();
const remaining = interval - (now - last);
clearTimeout(timer);
if (remaining <= 0) {
last = now;
fn.apply(this, args);
} else {
// make sure the final call still happens
timer = setTimeout(() => {
last = Date.now();
fn.apply(this, args);
}, remaining);
}
};
}
window.addEventListener('scroll', throttle(updateProgressBar, 100));
Which one?
- Debounce: you only care about the final value (search, validation, autosave, window resize end).
- Throttle: you need regular updates during the activity (scroll, mousemove, game input).
In React
const debouncedSave = useMemo(() => debounce(saveDraft, 1000), []);
useEffect(() => () => debouncedSave.cancel?.(), [debouncedSave]);
For visual updates tied to scrolling, requestAnimationFrame is often better than a time-based throttle, and for "is this element visible", use IntersectionObserver instead of scroll events altogether.
Was this article helpful?
Comments
No comments yet. Have a question or a tip? Be the first to share it.