← All concepts
A Closer Look at Functions

Debounce and Throttle

Two ways to tame a rapid-fire event: wait for quiet before reacting, or react on a steady schedule no matter how fast events arrive.

Definition

Debounce and throttle both wrap a function so it runs less often than the event that triggers it, but they make different promises. A debounced function waits for a pause: every new call resets a timer, and the wrapped function only actually runs once that timer finishes without being reset again, meaning it fires once, after the burst of calls has gone quiet. A throttled function makes a different promise: it runs at most once per fixed interval, no matter how many times it is called during that interval, so it keeps firing at a steady cadence throughout a burst instead of waiting for it to end. Both are typically built with closures over a timer id (or a last-run timestamp) that a rapid stream of calls shares, which is why they are usually implemented as higher-order functions: a function that takes your handler and returns a new, rate-limited version of it. Debounce suits input you only care about once it settles, like a search box waiting for someone to stop typing. Throttle suits input you want to keep responding to steadily even while it keeps happening, like a scroll or resize handler.

Examples

function debounce(fn, wait) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), wait);
  };
}
const onSearch = debounce((q) => runSearch(q), 300);
// typing "cat" fast only calls runSearch once, 300ms after the last keystroke

Every call clears the previous timer and starts a new one, so only the last call in a burst ever survives long enough to actually run fn.

function throttle(fn, wait) {
  let last = 0;
  return (...args) => {
    const now = Date.now();
    if (now - last >= wait) {
      last = now;
      fn(...args);
    }
  };
}
const onScroll = throttle(() => updatePosition(), 200);
// scrolling continuously still calls updatePosition about every 200ms

Each call checks how long it has been since the last run; only once wait milliseconds have passed does it run fn again and reset the clock.

Common mistakes

  • Reaching for throttle when you only care about the final state; debounce (fire once, after things settle) is usually the better fit.
  • Reaching for debounce on a scroll or drag handler, which then never fires at all while the user keeps moving continuously.
  • Forgetting to clear the pending timer when a debounced function's owning component unmounts, letting a stale call fire later.

Key takeaways

  • Debounce fires once, only after calls stop arriving for the configured wait.
  • Throttle fires repeatedly, at most once per fixed interval, throughout a burst.
  • Both are commonly built as a closure over shared timer or timestamp state, wrapping the original function.
  • Choosing between them depends on whether you want the final result only, or steady updates throughout.
Check your understanding

A user scrolls continuously for 2 seconds. A handler is throttled to run at most once every 500ms. Roughly how many times does the handler run?

Where you see this

  • Debouncing a search input so a network request only fires after the user stops typing.
  • Throttling a window scroll or resize listener so layout calculations run on a steady cadence instead of on every pixel of movement.
  • Debouncing a save-as-you-type feature so it writes once the user pauses, instead of on every keystroke.
  • Throttling a drag handler so position updates happen at a smooth, bounded rate instead of hundreds of times per second.

Practice this

Further reading

Previous
call, apply, and bind
Next
map, filter, and reduce