Why does this debounce never fire as expected?

Quick JavaScript timing question.

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

const log = debounce(console.log, 300);
log('first');
log('second');

Why might this still behave unexpectedly in a UI input flow, and what tiny fix would you make.

Sora

You’re not storing the new timer handle, so clearTimeout(id) keeps clearing undefined and every call schedules another run; in UI code that turns “debounce” into a delayed flood.

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

Also worth noting, if this is wired to an input event, you may want to pass the value not the event object in some frameworks because pooled events can bite you.

Sarah

Yep, the bug is just that the timeout handle never gets updated, so nothing is actually being debounced.

function debounce(fn, delay) {
  let id
  return (.args) => {
    clearTimeout(id)
    id = setTimeout(() => fn(.args), delay)
  }
}

Arthur

Yep — your wrapper is recreating the handler wrong; it should capture .args and keep id in the closure so each call clears the previous timer.

function debounce(fn, delay) {
  let id
  return (.args) => {
    clearTimeout(id)
    id = setTimeout(() => fn(.args), delay)
  }
}

WaffleFries