BitlystBitlyst
PostsSeriesTopicsAbout

Bitlyst

Short, diagram-first posts on JavaScript, React & Next.js. Follow via RSS or the newsletter.

© 2026 Mohsen Fallahnejad

PostsSeriesTopicsLinksAboutRSSGitHubX
Posts/javascript
Beginner4 min read

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.

Mohsen Fallahnejad

Mohsen Fallahnejad

Jul 8, 2026Discuss

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
js
function debounce(fn, delay = 300) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

Timeline#

User types: h → he → hel → hello (stops)

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

Three keystrokes, one API call.

React example#

tsx
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
js
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#

js
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.

Using throttle for search#

js
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#

tsx
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:

js
// 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#

  • useTransition — React's built-in way to defer heavy UI updates
  • useDeferredValue — defer expensive renders while typing
  • JavaScript Event Loop — how timers (setTimeout) fit in
Filed under#javascript#performance#debounce#throttle#frontend#interview

Was this worth your time?

One tap tells me what to write more of.

Mohsen Fallahnejad

Written by

Mohsen Fallahnejad

Frontend engineer explaining JavaScript, React & Next.js internals in posts you can finish over coffee. More about me

X ↗GitHub ↗LinkedIn ↗
← Older?? vs || — The Default-Value Trap That Breaks Real AppsNewer →CORS Explained Simply — Why Your Fetch Works in Postman but Fails in the Browser

Start the discussion

Questions, corrections, a better approach? I read and reply to every comment.

Read next

You Selected Rows 3, 8, 12, 15 and 20. JavaScript Checked 3, 1 and 8.

Why ['1', '7', '11'].map(parseInt) returns [1, NaN, 3]: map passes the index as parseInt's radix. Plus the Number('') trap hiding in the obvious fix.

Nov 1, 20264 min read2

The Leaderboard That Ranked 9 Points Above 100

Why JavaScript's sort() compares numbers as strings, why a 'lazy' comparator works in Safari but breaks in Chrome, and the one-line fix.

Oct 31, 20265 min read3

CORS Explained Simply — Why Your Fetch Works in Postman but Fails in the Browser

Cross-Origin Resource Sharing in plain language: what blocks your request, what preflight means, and how to fix the errors you'll see in every frontend job.

Jul 8, 20266 min read3

Get new posts in your inbox

No spam. Unsubscribe any time.

4 min left
Discuss👍 React
👍