debounce in js
Debounce in JavaScript
Optimizing high-frequency events by grouping bursts of calls into a single execution.
The Smart Hotel Elevator
Debouncing delays the execution of a function until a specified idle duration has elapsed since the last time the function was invoked. It prevents performance bottlenecks on resize, scroll, and input events.
Timer Management via Closures
The debounce wrapper maintains a `timeoutId` variable in its lexical environment. Each call clears the previous pending timer with `clearTimeout(timeoutId)`.
Scheduling the Next Execution
A new timer is scheduled with `setTimeout()`. If no subsequent calls arrive before the duration expires, the target function executes.
Preserving "this" and Arguments
The target function must be called with `fn.apply(context, args)` so that original event objects, parameters, and calling contexts are preserved.
Leading vs Trailing Edge Execution
Standard debounce runs on the trailing edge (after silence). Advanced debouncers support a leading edge trigger that fires immediately on the first call and cools down for subsequent bursts.
Production-ready debounce implementation with cancellation support:
function debounce(fn, delay = 300) {
let timerId = null;
const debounced = function(...args) {
const context = this;
if (timerId) clearTimeout(timerId);
timerId = setTimeout(() => {
fn.apply(context, args);
timerId = null;
}, delay);
};
debounced.cancel = function() {
if (timerId) {
clearTimeout(timerId);
timerId = null;
}
};
return debounced;
}
// Practical usage: Search Input Autocomplete
const searchApi = (query) => console.log("Searching database for:", query);
const handleSearch = debounce(searchApi, 200);
// Rapid keystrokes within 200ms
handleSearch("r");
handleSearch("re");
handleSearch("react"); // Only this final call executes after 200ms!
(After 200ms of typing inactivity)
Searching database for: react
Re-creating Debounced Function on Every Render in React
The Risk: Declaring `const debouncedFn = debounce(...)` inside a React component re-creates a new timer closure on every render, completely defeating the debounce.
The Fix: Wrap with `useCallback` or `useMemo`: `const debounced = useMemo(() => debounce(fn, 300), [])`, and cancel it on unmount.
- ✓ Debounce: Delays execution until silence has lasted for `delay` milliseconds.
- ✓ Best for: Search auto-suggest inputs, window resize refits, save draft autosave.
-
✓
Distinct from Throttle: Throttle guarantees execution every
Nms; debounce waits for stillness. - ✓ Always preserve `this` and arguments using `.apply(this, args)`.
- ✓ Provide a `.cancel()` method to clean up timers when components unmount.