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?