Bitlyst

Debounce vs Throttle — One Sentence to Remember Forever

Two patterns for taming fast events: debounce waits for silence, throttle limits how often. With examples, code, and interview answers.

Search boxes, scroll handlers, resize listeners, button spam — the browser fires events fast. Way faster than your API or UI can handle.

Two tools solve 90% of these problems:

Debounce = wait until things stop, then run once.
Throttle = run at most once per interval, no matter how noisy it gets.

Memorize that pair. You'll never confuse them in an interview again.


🧠 The Core Difference

PatternWhen it runsMental image
DebounceAfter activity pauses"Wait for silence, then act"
ThrottleOn a fixed schedule"At most once every N ms"

Debounce delays execution until the user stops triggering the event.
Throttle guarantees execution regularly, even during continuous activity.


🔍 Debounce — "Wait for Silence"

Use when: the final value matters, not every intermediate step.

Classic examples:

  • Search-as-you-type (wait until user stops typing)
  • Auto-save after editing stops
  • Window resize → recalculate layout once after drag ends
code
function debounce(fn, delay = 300) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

Timeline

User types: hhehelhello (stops)

code
h ── he ── hel ── hello ── [300ms silence] ── ✅ fetch("hello")
                         ↑ only this one runs

Three keystrokes, one API call.

React example

code
const debouncedSearch = useMemo(
  () => debounce((q: string) => fetch(`/api?q=${q}`), 300),
  []
);

function onChange(e: React.ChangeEvent<HTMLInputElement>) {
  debouncedSearch(e.target.value);
}

Or use a library: lodash.debounce, or useDeferredValue / useTransition in React 18+ for UI-specific cases.


⏱️ Throttle — "Once Per Interval"

Use when: you need regular updates during continuous activity.

Classic examples:

  • Scroll → update sticky header / progress bar
  • Mouse move → drag preview
  • Button spam → prevent double-submit
  • Infinite scroll → check position while scrolling
code
function throttle(fn, interval = 200) {
  let last = 0;
  return (...args) => {
    const now = Date.now();
    if (now - last >= interval) {
      last = now;
      fn(...args);
    }
  };
}

Timeline

User scrolls continuously for 2 seconds (interval = 200ms):

code
scroll scroll scroll ... → ✅ run → scroll scroll ... → ✅ run → ...
                           ~every 200ms max

Many scroll events, ~10 handler runs (not hundreds).


❌ Common Mistakes

Using debounce for scroll

code
window.addEventListener("scroll", debounce(updateProgress, 100));

User scrolls for 5 seconds → updateProgress runs once at the end.
Progress bar stays frozen until they stop. Feels broken.

✅ Use throttle for scroll.

code
input.addEventListener("input", throttle(search, 300));

User types "react" → searches "r", "re", "rea", "reac", "react".
Wasteful API calls; you only care about the final query.

✅ Use debounce for search.

Forgetting cleanup

code
useEffect(() => {
  const handler = throttle(onScroll, 200);
  window.addEventListener("scroll", handler);
  return () => window.removeEventListener("scroll", handler);
}, []);

Always remove listeners on unmount.


🆚 Side-by-Side

ScenarioPickWhy
Search inputDebounceOnly final query matters
Scroll positionThrottleNeed updates while scrolling
Window resizeDebounceRecalc layout once after resize ends
Button double-click guardThrottleBlock rapid repeats
Drag & drop previewThrottleSmooth updates during drag
Form auto-saveDebounceSave after user pauses typing
Analytics "scroll depth"ThrottleSample during scroll, not after

🎯 Interview Cheat Sheet

QuestionAnswer
What is debounce?Delay execution until events stop for N ms
What is throttle?Run at most once every N ms
Search box?Debounce
Scroll handler?Throttle
Leading vs trailing?Debounce usually trailing (after pause); throttle can be leading (first event fires immediately)
React alternative?useDeferredValue, useTransition for UI deferral — not the same, but related

🧪 Leading vs Trailing (Bonus)

Most debounce/throttle implementations are trailing — run after the wait period.

Some libraries support leading throttle — fire immediately, then ignore until interval passes:

code
// Leading throttle: first click works, then blocked for 1s
button.addEventListener("click", throttle(submit, 1000, { leading: true, trailing: false }));

Useful for "submit once, ignore spam clicks" UX.


✅ The One-Line Rule

Debounce = wait for quiet. Throttle = cap the rate.

When in doubt: ask "Do I care about the final value, or updates while it's happening?"

  • Final value → debounce
  • Updates during activity → throttle

Suggested follow-ups

You made it to the end!

Did this help? Leave a reaction — it takes one second.

 

Got feedback? 💬

Typo, suggestion, question — I read every message.

Comments

Mohsen Fallahnejad
Mohsen Fallahnejad

Writing bite-sized JS, React & Next.js tips

Get new posts in your inbox

No spam. Unsubscribe any time.