FossilsðŸ’ŧ CodingImplement Debounce
ðŸĢHatchlingJavaScriptUtilityClosures

Implement Debounce

The most common utility function interview question. Your implementation reveals whether you understand closures, timers, and edge cases.

Implement Debounce

Interview Question: "Implement a debounce function that delays invoking fn until after delay ms have elapsed since the last invocation."

The Core Implementation

function debounce(fn, delay) {
  let timeoutId;
 
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

Key points to articulate:

  • Closure over timeoutId — each debounced function has its own timer
  • clearTimeout before setTimeout — resets the clock on each call
  • fn.apply(this, args) — preserves this context and arguments
  • Returns a new function (higher-order function)

Production Version (with Cancel + Flush + Leading)

function debounce(fn, delay, { leading = false } = {}) {
  let timeoutId;
  let lastArgs;
  let lastThis;
 
  function invoke() {
    fn.apply(lastThis, lastArgs);
    lastArgs = lastThis = undefined;
  }
 
  function debounced(...args) {
    lastArgs = args;
    lastThis = this;
 
    if (leading && !timeoutId) {
      invoke();
    }
 
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      if (!leading) invoke();
      timeoutId = undefined;
    }, delay);
  }
 
  debounced.cancel = () => {
    clearTimeout(timeoutId);
    timeoutId = lastArgs = lastThis = undefined;
  };
 
  debounced.flush = () => {
    if (timeoutId) {
      clearTimeout(timeoutId);
      invoke();
      timeoutId = undefined;
    }
  };
 
  return debounced;
}

Follow-Up: "What's the difference between leading and trailing?"

  • Trailing (default): Fires after silence. User stops typing → function runs.
  • Leading: Fires immediately on first call, then ignores until delay passes. Button click → instant response, ignores rapid clicks.

Common Mistakes

  • Forgetting clearTimeout — timer stacks instead of resets
  • Losing this context — using arrow function for the outer return loses caller's this
  • Not handling cancel — real debounce functions need cleanup for unmounting