# Debouncing and Throttling

> Two related techniques for taming a firehose of events - waiting for a pause versus capping the rate - and how to pick between them.


---

# Debouncing and Throttling

A single keystroke, scroll, or mouse movement doesn't sound like much - but a keyboard can fire an event every hundred milliseconds while someone types, and a scroll or mousemove can fire hundreds of times a second while someone drags. A handler that does real work - hitting an API, recalculating a layout - on every one of those events does far more than the situation calls for. Debouncing and throttling are the two standard ways to bring that firehose under control, solving different versions of the problem.

## How to read this

Phase 1 lays out why the naive "run the handler on every event" approach is a real performance problem, with concrete examples. Phase 2 covers debounce - waiting for a pause before acting, the pattern behind every good search box. Phase 3 covers throttle - guaranteeing a maximum rate no matter how many events fire - and how to choose between the two.

## The phases

1. [The firehose problem](01-the-firehose-problem.md) - why running a handler on every event is a real performance problem.
2. [Debounce: wait for a pause](02-debounce.md) - the search-box pattern and the timer-reset idea.
3. [Throttle: cap the rate](03-throttle.md) - the scroll-handler pattern, and choosing between debounce and throttle.


---

# The firehose problem

Some events fire once, cleanly, when something happens - a button click, a form submission. Others fire in rapid, continuous bursts as a single ongoing action unfolds - every keystroke while someone types, every pixel of movement while someone scrolls or drags. The second category is where a perfectly reasonable-looking handler quietly becomes a performance problem.

## Search-as-you-type, hitting the API on every keystroke

Picture a search box that queries an API as the user types, so results update live instead of waiting for a submit button.

```js
searchInput.addEventListener("input", (event) => {
  fetchSearchResults(event.target.value);
});
```

*What just happened:* this looks correct, and functionally it is - every keystroke does trigger a fresh search, which is the intended behavior. But watch what happens when someone types a seven-letter word at a normal pace:

```text
user types "kubernet" ...

's' -> fetchSearchResults("k")
'u' -> fetchSearchResults("ku")
'b' -> fetchSearchResults("kub")
'e' -> fetchSearchResults("kube")
'r' -> fetchSearchResults("kuber")
'n' -> fetchSearchResults("kubern")
'e' -> fetchSearchResults("kuberne")
't' -> fetchSearchResults("kubernet")
```

*What just happened:* eight keystrokes fired eight separate API requests, seven of which were for a search the user never actually wanted - they wanted the last one, for the whole word. Each of those seven wasted requests still costs a full round trip: network latency, server processing, a database query, a response payload. Multiply this across every user typing every search on your site, and you're running server capacity almost entirely on throwaway queries for search terms nobody was actually searching for.

There's a second problem hiding here too: **out-of-order responses**. A slow network can mean the request for `"ku"` takes longer to come back than the request for `"kubernet"` that was fired six keystrokes later. If your code overwrites the results with whatever response arrives last, with no check for which one it actually is, the user can end up staring at results for `"ku"` even though they finished typing `"kubernet"` - because that response happened to land after the correct one.

## Scroll handlers firing hundreds of times a second

Scroll events are worse in a different way: not only do they fire often, they fire at a rate tied to the display's refresh rate and the speed of the scroll gesture, which can mean dozens to hundreds of firings per second during a fast scroll or drag.

```js
window.addEventListener("scroll", () => {
  updateParallaxPosition();   // recalculates and repaints an element
});
```

*What just happened:* `updateParallaxPosition` does real work - reading the scroll position, computing a new transform, applying it to the DOM. If the scroll event fires 200 times in the second it takes to flick-scroll down a page, that function runs 200 times, each run reading layout information, writing a style change, and potentially triggering a repaint. Do enough of this and the page starts to visibly stutter, because a browser typically renders around 60 frames per second - anything beyond roughly 60 updates in that second was never going to be visible anyway.

```text
scroll event fires:        ~200 times in one second of fast scrolling
frames the browser draws:   ~60 times in that same second
work that was actually
visible to the user:        at most 60 of those 200 updates
```

*What just happened:* more than two-thirds of the work in that second produced no visible difference to the user - it was calculated, applied, and then immediately overwritten by the next update before a frame was ever drawn showing it. That's not a subtle inefficiency; it's the majority of the work being thrown away by construction.

## The shared shape of the problem

Both examples - the search box and the scroll handler - share the same underlying issue: **the event fires far more often than the desired outcome needs to happen.** Nobody wants search results for every partial word typed on the way to a full one, and nobody can perceive a parallax update happening more often than the screen can redraw. Running the handler on literally every event does work in service of a granularity nobody asked for and nobody benefits from.

> The event firing isn't the problem. Running expensive work on every single firing, when only the last one (or a bounded number per second) actually matters, is the problem.

That distinction - "wait until things settle" versus "allow updates, but cap how often" - is exactly the fork between the two techniques ahead. Debounce handles the first shape: a burst of events where only the final one matters. Throttle handles the second: an ongoing stream where you want steady updates, capped at a bounded rate rather than an unbounded one.


---

# Debounce: wait for a pause

The search box from Phase 1 has a burst of events (one per keystroke) where only the very last one in the burst actually matters - the finished word or phrase. **Debouncing** is built exactly for this shape: it waits for a pause in the events before acting, and every new event within that waiting window cancels and restarts the wait.

> Debounce means: keep pushing the action back as long as new events keep arriving. Only act once things go quiet.

## The timer-reset idea

The mechanism is a single timer that gets reset on every event.

```text
each new event:
  1. cancel any timer that's currently waiting
  2. start a brand new timer for, say, 300ms
  3. when a timer finally finishes without being cancelled,
     THAT's when the real action runs
```

*What just happened:* as long as events keep arriving faster than the timer's delay, no timer ever gets the chance to finish - each new event kills the previous timer before it can fire and starts a fresh one. The moment there's a gap longer than the delay, whichever timer is currently running finally completes uninterrupted, and that's the one and only time the action fires.

Applied to the search box's eight keystrokes from Phase 1, with a 300ms debounce delay:

```text
's' at 0ms    -> timer set for 300ms
'u' at 90ms   -> cancel previous timer, set new one for 390ms
'b' at 180ms  -> cancel previous timer, set new one for 480ms
'e' at 270ms  -> cancel previous timer, set new one for 570ms
'r' at 340ms  -> cancel previous timer, set new one for 640ms
'n' at 410ms  -> cancel previous timer, set new one for 710ms
'e' at 480ms  -> cancel previous timer, set new one for 780ms
't' at 550ms  -> cancel previous timer, set new one for 850ms
... user stops typing ...
850ms         -> timer finally completes -> fetchSearchResults("kubernet") fires
```

*What just happened:* eight keystrokes, one API call, fired 300ms after the last keystroke - not 300ms after the first one, and not on some fixed schedule. If the user had kept typing at that pace for another twenty letters, the debounced action still wouldn't fire until 300ms after they finally stopped. The delay window follows the *last* event, always.

## Implementing it

The pattern behind most debounce implementations is small enough to write from scratch - worth doing once so the library-provided versions don't feel like magic:

```js
function debounce(fn, delayMs) {
  let timeoutId;

  return function (...args) {
    clearTimeout(timeoutId);              // cancel any pending timer
    timeoutId = setTimeout(() => {
      fn(...args);                        // run the real function
    }, delayMs);
  };
}

const debouncedSearch = debounce(fetchSearchResults, 300);

searchInput.addEventListener("input", (event) => {
  debouncedSearch(event.target.value);
});
```

*What just happened:* `debounce` wraps `fetchSearchResults` and returns a new function, `debouncedSearch`, that the input listener calls on every keystroke. Internally, that wrapper does exactly the cancel-and-restart dance from the diagram above: `clearTimeout` cancels whatever timer is currently waiting (if any), and `setTimeout` starts a fresh one. `fn(...args)` - the actual `fetchSearchResults` call - only ever runs from inside a timer that was allowed to complete, which only happens after 300ms of silence.

## Where else this pattern fits

The search box is the canonical example, but the same shape - a burst of events where only the final state matters - shows up wherever a user is actively adjusting something and you want to react once they've settled on a value, not on every intermediate step:

- **Window resize handlers** that recalculate an expensive layout. Dragging a window's edge fires dozens of resize events; you want the recalculation once the user releases the edge and the size stops changing, not on every pixel of the drag.
- **Auto-save in a text editor.** You don't want to save to a server on every keystroke - you want to save once the user pauses, which is exactly a debounced "on change" handler.
- **A form field validating itself as the user types**, where showing a "this email looks wrong" error on every half-typed character would be more annoying than helpful - waiting for a pause gives the user room to actually finish typing first.

All three share the same reasoning as the search box: the events arrive in a rapid burst, and reacting to the burst's end - not its every step - is both cheaper and more correct for what the user actually wants.


---

# Throttle: cap the rate

Debounce is the wrong tool for the scroll handler from Phase 1: it would only run once scrolling *stops*, but a parallax effect or a "sticky header that fades in" needs to update *while* the user is scrolling. Waiting for a pause would mean the page visibly does nothing for the entire scroll, then suddenly catches up all at once - not what anyone wants.

**Throttling** solves a different problem: guarantee the handler runs at most once every fixed interval, no matter how many events fire in that interval. Events keep being allowed through at a steady, capped rate, instead of being collapsed down to a single one at the end.

> Throttle means: let the action run regularly, but never more often than a fixed interval, no matter how fast the events are actually arriving.

## The rate-cap idea

The mechanism tracks whether the interval has elapsed since the last time the action ran, and ignores events that arrive before it has.

```text
each new event:
  1. has at least `interval` ms passed since the action last ran?
  2. if yes -> run the action now, record this moment as "last ran"
  3. if no  -> ignore this event entirely, do nothing
```

*What just happened:* unlike debounce, nothing gets rescheduled or delayed here - an event either qualifies to trigger the action right now, or it's dropped. There's no waiting for quiet; there's only a gate that only opens once every `interval` milliseconds.

Applied to a scroll handler throttled to 100ms, during one second of continuous fast scrolling that fires roughly 200 scroll events:

```text
scroll events arrive:     roughly every 5ms (200 events in 1000ms)
throttle interval:        100ms

event at 0ms    -> 100ms have passed since last run (none yet) -> RUN, last ran = 0ms
events 5-95ms   -> less than 100ms since last run -> ignored (~19 events)
event at 100ms  -> exactly 100ms since last run -> RUN, last ran = 100ms
events 105-195ms -> ignored (~19 events)
event at 200ms  -> RUN, last ran = 200ms
... repeats every 100ms ...
```

*What just happened:* out of roughly 200 events in that second, only about 10 ran the handler - one every 100ms, like clockwork. Compare that to debounce, which would have produced zero runs during the scroll and exactly one run after it stopped. Throttle keeps the page updating *throughout* the scroll, at a rate the eye can actually follow, instead of either running 200 times (wasteful) or 0 times until the end (unresponsive).

## Implementing it

```js
function throttle(fn, intervalMs) {
  let lastRan = 0;

  return function (...args) {
    const now = Date.now();
    if (now - lastRan >= intervalMs) {
      lastRan = now;
      fn(...args);                 // run the real function
    }
    // otherwise: this event is dropped, nothing happens
  };
}

const throttledParallax = throttle(updateParallaxPosition, 100);

window.addEventListener("scroll", throttledParallax);
```

*What just happened:* `lastRan` remembers the timestamp of the most recent time `fn` actually ran. Every call checks `now - lastRan` against the interval - if enough time has passed, it runs `fn` and updates `lastRan`; if not, the call does nothing at all. Note the structural difference from `debounce`'s implementation in Phase 2: debounce always eventually calls `fn` (once things go quiet), while throttle may call `fn` many times across a long event stream, spaced out, and drops whichever events land in between without rescheduling them.

A common refinement is to also fire on the *last* event of a burst even if it lands inside the cooldown window, so the final state (like the exact scroll position where the user stopped) still gets reflected - but the core mechanism above is the part worth understanding first.

## Choosing between them

The two techniques answer different questions, and the question your situation is actually asking determines which one fits:

```text
"Do I only care about the FINAL state, once things settle?"
  -> DEBOUNCE (search box, auto-save, resize-triggered relayout)

"Do I need ONGOING updates throughout the activity, bounded to
 a fixed rate rather than unbounded?"
  -> THROTTLE (scroll effects, drag-to-resize previews, mousemove
     tracking for a cursor trail)
```

*What just happened:* the search box never benefits from an update mid-typing - a partial word isn't a valid search, so waiting for the pause is strictly correct, not a performance shortcut taken at the expense of correctness. A parallax effect is the opposite: it needs to look continuous *during* the scroll, so collapsing it down to one update after scrolling stops would make the effect disappear entirely while it matters most.

A shorthand worth keeping: **debounce waits for silence; throttle keeps things flowing but capped.** If your instinct says "I want this to feel continuous while it's happening," reach for throttle. If your instinct says "I only care what things look like once they stop changing," reach for debounce. "Run the handler on every event" was never really the requirement in either case - naming which kind of "less often" you need is what picks the tool.

```quiz
[
  {
    "q": "What does a throttled function do when an event arrives before the interval has elapsed since the last run?",
    "choices": [
      "It queues the event and runs it later, once the interval passes",
      "It ignores that event entirely - the call does nothing and is not rescheduled",
      "It runs the function immediately anyway",
      "It resets the interval timer, the same way debounce does"
    ],
    "answer": 1,
    "explain": "Throttle drops events that arrive inside the cooldown window rather than queuing or rescheduling them. This is the key structural difference from debounce, which reschedules on every event."
  },
  {
    "q": "Why is debounce the wrong choice for a scroll-triggered parallax effect?",
    "choices": [
      "Debounce is always slower than throttle",
      "Debounce would only trigger once scrolling fully stops, so the effect would be invisible during the scroll itself, when it needs to be seen",
      "Debounce can't be used with scroll events at all",
      "Parallax effects require more than one event listener"
    ],
    "answer": 1,
    "explain": "Debounce collapses a burst down to a single action after things go quiet. A parallax effect needs continuous updates while scrolling is happening, which is exactly what throttle provides instead."
  },
  {
    "q": "What's the shorthand for choosing between debounce and throttle?",
    "choices": [
      "Debounce is for images, throttle is for text",
      "Debounce waits for silence and acts once at the end; throttle keeps updates flowing but caps how often they happen",
      "Debounce is faster, so always prefer it",
      "Throttle is only for mobile devices"
    ],
    "answer": 1,
    "explain": "If only the final settled state matters, debounce fits. If ongoing, continuous-feeling updates are needed throughout an activity, throttle fits."
  }
]
```

Watch it animated: [debouncing](/explainers/Debouncing.dc.html)
