Debouncing is a programming pattern that limits the rate at which a function gets invoked. It is particularly valuable for handling high-frequency events like keystroke searches, window resizing, or scroll tracking.
The Code
export function debounce<T extends (...args: any[]) => any>(
fn: T,
wait: number = 300
): ((...args: Parameters<T>) => void) & { cancel: () => void } {
let timeoutId: ReturnType<typeof setTimeout> | null = null;
const debounced = (...args: Parameters<T>) => {
if (timeoutId !== null) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
fn(...args);
timeoutId = null;
}, wait);
};
debounced.cancel = () => {
if (timeoutId !== null) {
clearTimeout(timeoutId);
timeoutId = null;
}
};
return debounced;
}How It Works
- Timer Reset: Every time the returned debounced function is executed, any currently scheduled timer is canceled via
clearTimeout. - Delayed Execution: A new timer is set to execute the target function only after the specified
waitmilliseconds pass without another call. - Cancellation Method: Attaching
.cancel()allows callers (such as ReactuseEffectcleanups) to discard pending timers before a component unmounts. - Strong Typing: Generics ensure the debounced function retains the exact argument types of the original function.
Example Usage
const handleSearch = (query: string) => {
console.log(`Searching for: ${query}`);
};
const debouncedSearch = debounce(handleSearch, 400);
// Typing "react":
debouncedSearch("r");
debouncedSearch("re");
debouncedSearch("rea");
debouncedSearch("react");
// Only executes once with "react" after 400ms of inactivity!