HAMZA KAREEM / CONCEPTS hamzawi.dev

Lesson 11 min read

Debouncing vs Throttling

two fixes for "this fires way too often"

Web developmentweb development / frontend

Both tame a function that runs too much (keypress, scroll, resize, autocomplete calls). They are NOT interchangeable — pick the wrong one and the UI feels laggy or broken.

The one-line difference

  • Debounce = wait for quiet. Run only AFTER events stop for N ms.
  • Throttle = steady drip. Run at most once every N ms while events keep coming.

Which one, when

  • Search-as-you-type → debounce ~300ms. You only care about the final query, so fire the API when the user pauses. (200 keystrokes ≠ 200 requests.)
  • scroll / mousemove / resize → throttle ~100ms. You want regular updates as it happens, just not 200×/sec.
  • "Auto-save draft" → debounce. Infinite-scroll loader → throttle.

Minimal versions (5 lines each)

const debounce = (fn, ms) => {
  let t;
  return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms); };
};

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

Same skeleton, one real difference: debounce RESETS the timer on every call (so it never runs until you stop), while throttle checks elapsed time and lets calls through on a fixed cadence.

The gotcha that trips people up

Leading vs trailing edge. The debounce above is trailing-only — it fires after the pause. So if a user types then hits Enter, a stale trailing call can still fire AFTER navigation. For a "don't double-submit" button you usually want the LEADING edge instead: fire immediately, then ignore repeats. Mixing these up is the classic "my debounced search fires one extra time" bug.

⚛️ React trap: don't recreate the debounced function on every render — a new closure resets the timer each time, so it never fires. Wrap it in useMemo / useRef.

Your exercise

Add a { leading: true } option to the debounce above so it fires on the FIRST call, then stays silent until things go quiet for ms. Then: for a "Load more" infinite-scroll trigger, would you reach for debounce or throttle — and why?