principlejavascriptTip
Debounce vs throttle: choosing the right rate limiter
Viewed 0 times
debouncethrottlerate limitingscroll handlersearch inputevent optimization
Problem
Debounce and throttle are used interchangeably. Debouncing a scroll handler means it never fires during continuous scrolling. Throttling a search input means the API is called on every keystroke instead of after the user pauses.
Solution
Use debounce when you want to react after activity stops. Use throttle when you want to react at a maximum rate during continuous activity.
// Debounce: fires AFTER user stops typing for 300ms
const debouncedSearch = debounce((query) => {
fetchResults(query);
}, 300);
input.addEventListener('input', e => debouncedSearch(e.target.value));
// Throttle: fires AT MOST once per 100ms during scroll
const throttledScroll = throttle(() => {
updateScrollProgress();
}, 100);
window.addEventListener('scroll', throttledScroll);
// Minimal implementations
function debounce(fn, delay) {
let id;
return (...args) => {
clearTimeout(id);
id = setTimeout(() => fn(...args), delay);
};
}
function throttle(fn, limit) {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall >= limit) {
lastCall = now;
fn(...args);
}
};
}
// Debounce: fires AFTER user stops typing for 300ms
const debouncedSearch = debounce((query) => {
fetchResults(query);
}, 300);
input.addEventListener('input', e => debouncedSearch(e.target.value));
// Throttle: fires AT MOST once per 100ms during scroll
const throttledScroll = throttle(() => {
updateScrollProgress();
}, 100);
window.addEventListener('scroll', throttledScroll);
// Minimal implementations
function debounce(fn, delay) {
let id;
return (...args) => {
clearTimeout(id);
id = setTimeout(() => fn(...args), delay);
};
}
function throttle(fn, limit) {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall >= limit) {
lastCall = now;
fn(...args);
}
};
}
Why
Debounce collapses rapid events into one call at the trailing edge. Throttle guarantees calls are spaced by at least N milliseconds. Wrong choice: debounced scroll handler fires 0 times per scroll; throttled search fires on every keystroke.
Gotchas
- Debounced functions should be created once (at component mount or module level), not inside event handlers
- React: create debounced functions with useRef or useMemo to avoid recreating them on every render
- lodash debounce has a leading option to fire immediately on first call as well
- Cleanup: cancel pending debounced calls on unmount to prevent setState after unmount
Code Snippets
Stable debounced function in React with cleanup
// React: stable debounced function with cleanup
const debouncedFn = useRef(
debounce((value) => search(value), 300)
).current;
useEffect(() => {
return () => debouncedFn.cancel(); // lodash cancel
}, [debouncedFn]);Context
When attaching event listeners to scroll, resize, input, or mousemove events
Revisions (0)
No revisions yet.