← Concepts

阅读中文版本

Debounce and Throttle

Debounce delays work until input stops; throttle caps how often work runs. They solve different burst patterns and are not interchangeable.

performance

Definition

Debounce postpones a function until a quiet period has elapsed since the last call, collapsing a burst of events into one invocation. Throttle guarantees a maximum invocation rate by running at most once per interval, regardless of how many calls arrive. Both reduce work caused by high-frequency events, but debounce optimizes for the final state while throttle optimizes for steady progress.

Context and scope

Use debounce for search input, form validation, and resize handlers where only the settled value matters. Use throttle for scroll position, pointer movement, and progress reporting where periodic updates are required. Choosing the wrong one either drops updates users expect or delays the only update they need.

Common misconceptions

  • Debounce and throttle are interchangeable — debounce waits for silence, throttle emits at a bounded rate
  • Debounce always delays execution — leading-edge debounce runs immediately and suppresses the trailing call
  • Throttle drops every call in between — implementations may queue a trailing call so the last value is not lost

Anti-patterns

  • Debouncing a scroll handler that must update a sticky header continuously
  • Throttling a search box without a trailing call, so the final query never runs
  • Recreating the debounced function on every render, which resets its timer and defeats the purpose

Related concepts

Related examples

Authoritative source

https://lodash.com/docs/4.17.15#debounce

What each one guarantees

Debounce answers “run this once the caller has stopped calling.” Each new call resets the timer. A search box that fires on every keystroke should query once after the user pauses, not once per character.

Throttle answers “run this at most once per interval.” A scroll handler that reads scrollY can run every 100 ms even while the user scrolls continuously.

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

function throttle(fn, interval) {
  let last = 0;
  return (...args) => {
    const now = Date.now();
    if (now - last >= interval) {
      last = now;
      fn(...args);
    }
  };
}

When to use which

Use debounce when intermediate values have no value: search suggestions, autosave, validation after typing. Use throttle when intermediate values matter: scroll-linked animation, analytics sampling, drag coordinates.

Compared to a rate limiter, both are client-side ergonomics controls. A server-side rate limiter protects shared capacity; debounce and throttle protect the browser and downstream APIs from unnecessary work.

Pitfalls

The most common bug is recreating the wrapper inside a render or event registration, which resets the timer on every call. Create it once and reuse it. The second is forgetting the trailing call: a pure leading-edge throttle can drop the final value, which is often the one the user cares about.