panchawat.me
← back

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

debounce.ts
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

usage.ts
const log = debounce((query: string) => {
  console.log(`searching for: ${query}`);
}, 300);
 
log("c");
log("cl");
log("clau"); // only this one survives the 300ms window

Only 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.