Frontend
medium
mid
How to implement debouncing
Debounce delays running a function until it stops being called for a set wait time — each call resets a timer. Implement with a closure over a timer id: clear the previous timeout and set a new one. Used for search-as-you-type, autosave, and resize handlers.
6 min read·~12 min to think through
Debouncing ensures a function runs only after a pause in calls — every new call resets the wait timer. "Wait until the user stops doing the thing, then run once."
Core implementation
js
function debounce(fn, wait) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId); // cancel the pending call
timeoutId = setTimeout(() => fn.apply(this, args), wait); // schedule a new one
};
}
// usage
const onSearch = debounce((q) => fetchResults(q), 300);
input.addEventListener("input", (e) => onSearch(e.target.value));The mechanics:
- A closure holds
timeoutIdacross calls. - Each call clears the previous pending timeout and sets a new one.
fnonly fires once calls stop forwaitms.apply(this, args)preservesthisand forwards arguments.
Production-quality version — add the useful options
js
function debounce(fn, wait, { leading = false, trailing = true } = {}) {
let timeoutId;
function debounced(...args) {
const callNow = leading && !timeoutId;
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
timeoutId = null;
if (trailing && !callNow) fn.apply(this, args);
}, wait);
if (callNow) fn.apply(this, args);
}
debounced.cancel = () => { clearTimeout(timeoutId); timeoutId = null; };
return debounced;
}leading— fire immediately on the first call, then debounce the rest.trailing— fire after the pause (the default).cancel()— important so React effects can clean up a pending call on unmount.
Debounce vs throttle (always clarify)
- Debounce — waits for calls to stop; fires once after quiet. Search input, autosave, validation, resize-settled.
- Throttle — fires at most once per interval during continuous calls. Scroll handlers, mousemove, drag, rate-limiting.
"Debounce = run after the storm; throttle = run every N ms during the storm."
Using it in React
jsx
const debouncedSearch = useMemo(() => debounce(search, 300), []);
useEffect(() => () => debouncedSearch.cancel(), [debouncedSearch]); // cleanupuseMemoso it isn't recreated each render (a fresh debounce loses its timer).- Cancel on unmount or a pending call fires on a gone component.
- Or use a
useDebouncehook that debounces a value viasetTimeoutin an effect.
Edge cases / gotchas
- Recreating the debounced fn each render resets its state — memoize it.
- Not cancelling on unmount → "setState on unmounted component" / stale call.
thisbinding —applyhandles it; arrow-function gotchas if you're careless.- Leading+trailing interaction needs care (handled above).
Follow-up questions
- •What's the difference between debounce and throttle, and when do you use each?
- •How do you debounce correctly inside a React component?
- •Why does the debounced function need a cancel method?
- •What do leading and trailing options do?
Common mistakes
- •Recreating the debounced function every render so it never actually debounces.
- •Not cancelling the pending call on unmount.
- •Confusing debounce with throttle.
- •Losing this/arguments by not using apply.
Performance considerations
- •Debouncing collapses a burst of events into a single execution — critical for API calls (search-as-you-type), expensive computations, and validation. The wait value tunes responsiveness vs. work: too short and it barely helps, too long and it feels laggy.
Edge cases
- •Component unmounts with a call still pending.
- •leading + trailing both enabled.
- •The debounced function needs to be cancelled or flushed immediately.
- •Rapid calls that should still eventually run exactly once.
Real-world examples
- •Search-as-you-type firing one API call after the user pauses.
- •Autosave after the user stops typing; validating a form field on input-settled.
Senior engineer discussion
Seniors implement it cleanly with a closure, then extend to leading/trailing/cancel and explain the React pitfalls (memoize the instance, cancel on unmount). They always contrast debounce vs throttle by use case, and know debounce-the-value (effect + timeout) as the idiomatic React-hook alternative.
Related questions
Frontend
Medium
5 min
Frontend
Easy
6 min
Frontend
Easy
7 min