← Daily Logs
GreatFrontEnd

Implement Debounce

EasyJul 1, 2026timersclosurerate-limiting

Wrap a function so it only fires after a quiet gap — every new call resets the clock, so a burst of activity collapses into one invocation once things settle.

The problem

Write `debounce(func, wait)` that returns a debounced version of `func`. Each call schedules `func` to run after `wait` milliseconds, but any call that arrives before the delay elapses cancels the pending run and restarts the timer.

The upshot: fire the wrapper ten times in quick succession and `func` runs exactly once, `wait` ms after the last call. It's the standard tool for taming high-frequency events like keystrokes, resize, and scroll.

The approach

A closure holds a single `timer` id that persists across calls to the returned function — that shared, mutable handle is the whole mechanism. Each invocation first does `clearTimeout(timer)` to cancel whatever was pending, then schedules a fresh `setTimeout`.

Because every call clears the previous timeout before setting a new one, only the final call in a burst survives to actually fire — the earlier ones are cancelled mid-flight. The trailing `func(...args)` runs with the most recent arguments, since each call overwrites what the pending timer would have used.

Honest caveat about this version: the arrow function doesn't rebind `this`, so it captures the enclosing `this` rather than the caller's. A fully spec-faithful debounce uses a regular `function` and `func.apply(this, args)` so it works as a method. For plain standalone functions, this implementation is fine.

The solution

js
/**
 * @param {(...args: Array<unknown>) => unknown} func
 * @param {number} wait
 * @returns {(...args: Array<unknown>) => void}
 */
export default function debounce(func, wait) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      func(...args);
    }, wait);
  };
}

Time O(1)Space O(1)

All entries