skip to content

Request Races & Cancellation

Which response is allowed to reach the tree when input changes faster than the server answers: latest-wins by stale flag or abort, abort on teardown, dedup, debounce. A classic search-box trap.

on this pageshow

questions

5

When a component starts a request on every keystroke in a text field, how do debouncing and throttling differ?

level: juniorimportance: must knowfreq 70%

answer

  1. how many requests start, not which lands
  2. one timer that every event resets
  3. quiet gap versus fixed maximum rate
  4. trailing edge fires once, after typing stops
  5. still needs a latest-wins rule on top

basics

~20 s

Debouncing waits for a quiet gap after the last keystroke and then fires once; throttling fires at a fixed maximum rate while typing continues. A search field wants debouncing, because only the settled query matters.

solid answer

~40 s

Both reduce how many requests start, by different rules. A **debounce** holds a single timer that every keystroke resets, so the request fires only after typing has paused for the chosen interval — one request per burst, carrying the settled value. A **throttle** enforces a ceiling instead: while events keep arriving it fires at most once per interval, so requests go out mid-word and the user sees intermediate results. A search field wants a debounce, since results for `hel` on the way to `hello` are waste; a continuously dragged control wants a throttle, since its intermediate values are what the user is looking at. Neither one decides which response is allowed to land: they change the number of requests, not their ordering, so a latest-wins rule is still required on top.

go deeper

for a junior

Be able to state both rules in one sentence each and name a fitting use: debounce waits for a pause and fires once, throttle fires at a steady maximum rate while events keep coming.

for a middle

Explain the mechanics: one resettable timer versus a rate window, trailing versus leading edge, and why the trailing run must carry the newest value rather than the one captured first.

for a senior

Show that a timer is a volume control, not a correctness control. Pair it with a latest-wins rule and cancellation, clear the pending timer on teardown, and justify the interval from measured typing behaviour.

for a principal

Frame it as policy: which inputs get which rule, what the default interval is, and whether the timer sits at each call site or inside a shared request layer so the choice is not re-argued per screen.

## The problem both timers solve A text field emits one input event per keystroke. If every event starts a request, a ten-character query starts ten requests. Most of them answer a query the user never meant to ask, and because they all write into the same place they create a race over which response ends up on screen. **Debouncing** and **throttling** both cut the number of requests that start. They are different rules and they are not interchangeable. ## What a debounce does A debounce waits for silence. Each event resets one pending timer, and the work runs only when the timer expires with no further event — the **trailing edge** of a burst. - While the user types quickly, nothing starts at all. - One request starts once the user pauses for the chosen interval. - A keystroke during the wait replaces the pending run, so only the latest value survives. - A leading-edge variant fires on the first event and suppresses the rest of the burst. That suits an action that must feel instant, such as guarding a repeatedly clicked trigger; it is rarely what a search field wants. ## What a throttle does A throttle enforces a maximum rate. While events keep arriving, the work runs at most once per interval: the first event fires, later events inside the window are dropped or collapsed into a single run at the window's end. - Work happens *during* the burst, at a steady cadence. - The user sees sampled intermediate states rather than only the settled one. - The number of runs grows with how long the burst lasts, not with how dense it is. ## Choosing between them | | Debounce | Throttle | |---|---|---| | Fires | after the burst stops | during the burst, at a fixed rate | | Runs per burst | one, on the trailing edge | one per interval | | Intermediate values | discarded | sampled | | Fits | input whose only meaningful value is the settled one | a stream whose intermediate values are meaningful | | Typical use | search field, validation while typing, saving a draft | pointer movement, scroll position, resize-driven layout | The deciding question is simple: **does an intermediate value deserve a request?** For a search field the answer is no, so debounce. For a map that reloads markers while the viewport is dragged, the intermediate viewports are exactly what the user is looking at, so throttle. ## What a timer does not fix This is the part interviewers probe, because candidates routinely stop one step early. A timer changes **how many** requests start. It says nothing about **which response is allowed to reach the tree**. 1. Two debounced requests can still overlap: the user pauses, a request starts, the user types again, a second starts, and the first answers second. Without a latest-wins rule — tag each request and ignore anything that is not the newest, or cancel the superseded one — the older response overwrites the newer data. 2. A throttle guarantees overlap by design, since it deliberately keeps firing while an earlier run may still be pending. 3. A longer delay lowers the probability of a stale overwrite without removing it. Correctness that depends on timing is not correctness. So the honest answer pairs the two mechanisms: a timer for **volume**, and a latest-wins rule for **ordering**, plus cancellation of superseded work when letting it finish costs something worth saving. ## Practical details - **Pick the interval from human behaviour.** Roughly 150-300 ms of quiet reads as responsive for a search field; much beyond half a second and the field feels dead. Measure with real typists instead of arguing about it. - **The timer belongs to whatever owns the input.** When that instance is destroyed, the pending timer must be cleared, or it wakes up later and starts a request for a screen nobody is looking at. - **Debounce the request, never the displayed value.** The field must echo every keystroke immediately; delaying the rendered text makes the input feel broken. - **A trailing run must carry the latest argument.** A debounce that captured the first event's value and fires with it defeats its own purpose, so capture the value at fire time or overwrite the captured value on every event. - **Where the timer lives differs by reactivity model:** a runtime that re-runs the whole component function on every state write will recreate a naively declared timer on each pass unless it is held across passes; a runtime with fine-grained tracking re-runs only the reacting piece and keeps it naturally; a compile-time runtime can hide the difference. The rule that survives all three is the same — one long-lived timer per input, created once and cleared on teardown.

  • If a debounce already cuts a search field to one request per pause, why is a latest-wins guard still needed?
    Because pauses produce overlapping requests. The user pauses, a request starts, then types again and a second starts; the first can answer second and overwrite the newer data. A timer controls how many requests exist, not the order their responses land in, so the newest request still has to be the only one allowed to write.
  • When would you fire on the leading edge of a burst instead of the trailing edge?
    When the first event is the meaningful one and the rest are noise: a repeatedly clicked trigger, a keyboard shortcut held down, or any action whose feedback must feel immediate. Leading-edge firing acts at once and then suppresses the burst, whereas trailing-edge firing always costs the user the full delay before anything happens.
  • How would you pick the debounce interval for a search field?
    Start from typing cadence: the gap should be long enough to sit between keystrokes of a fluent typist and short enough that a deliberate pause feels answered, which in practice lands near 150-300 ms. Then validate against real usage — count how many requests a typical query produces and how long users wait before the first result.

A debounce is an elevator door that closes only after nobody has stepped in for three seconds; a throttle is a metro that leaves every two minutes no matter how many people are arriving.

saying these in an interview costs you the question

  • Uses debounce and throttle as two names for the same timer
  • Claims debouncing removes out-of-order responses, so no latest-wins rule is needed
  • Throttles a search field, so requests go out for half-typed queries
  • Delays the field's displayed value along with the request, making typing feel broken
  • Leaves the pending timer running when the input's owner is destroyed
  • Fires the trailing run with the value captured at the start of the burst
open as a page

Why can an earlier response overwrite newer data when a component restarts its request after its input changes?

level: middleimportance: must knowfreq 78%

basics

~20 s

Responses can come back in a different order than the requests went out, and both write to the same state, so the slower earlier one lands last and wins. A latest-wins rule fixes it: only the newest request may write.

open as a page

How does a shared request layer stop three components that ask for the same resource at once from sending three requests?

level: middleimportance: should knowfreq 58%

basics

~20 s

By keying requests in flight: the first ask starts the request and stores its pending result under a key derived from the request; identical asks arriving while it is pending attach to that same pending result.

open as a page

When the component that started a request is destroyed before the response arrives, should that request be cancelled?

level: seniorimportance: should knowfreq 55%

basics

~10 s

It depends who owns the request. A read whose only consumer was that instance should be cancelled; a read a shared layer keeps for other consumers should finish. A write already sent must complete.

open as a page

Should latest-wins and cancellation rules live in each component that requests data, or in one shared request layer?

level: principalimportance: should knowfreq 44%

basics

~20 s

Put the policy in one shared layer once more than a handful of screens race: request identity, latest-wins, deduplication and cancellation then hold by default rather than per call site. Per-component guards suit only one-off, screen-private requests.

open as a page