Writing a Debounce in TypeScript
typescript · programming
Debouncing collapses a burst of rapid calls into a single one that fires after things go quiet. It's the classic fix for search-as-you-type, resize handlers, and autosave. Here's a small generic implementation.
The implementation
export function debounce<Args extends unknown[]>(
fn: (...args: Args) => void,
delayMs: number
): (...args: Args) => void {
let timer: ReturnType<typeof setTimeout> | undefined;
return (...args: Args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delayMs);
};
}The generic Args ties the returned function's parameters to the original, so
calling it with the wrong arguments is a compile error — not a runtime surprise.
Trying it out
const log = debounce((query: string) => {
console.log(`searching for: ${query}`);
}, 300);
log("c");
log("cl");
log("clau"); // only this one survives the 300ms windowOnly the final call inside the quiet window actually runs. Everything typed before it is discarded:
Console
searching for: clau
Notice the highlighted lines 5-7 above — rehype-pretty-code lets
you emphasize a range right from the code fence.
Why this pattern holds up
- Type-safe — the wrapper preserves the original signature.
- Allocation-light — one closure, one timer handle.
- Framework-agnostic — no React, no dependencies; drop it anywhere.