skip to content

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

level: middleimportance: must knowfreq 78%

answer

  1. requests are ordered, responses are not
  2. two handlers, one state slot
  3. prove you are still wanted before writing
  4. tag per request compared on arrival
  5. cancellation is efficiency, tagging is correctness

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.

solid answer

~50 s

Changing the input starts a second request while the first is still in flight, and nothing guarantees the server answers in the order it was asked — a cheap later query can return before an expensive earlier one. Both handlers write to the same state, so whichever lands last wins, and that may be the response for the input the user already replaced. There are two ways to enforce latest-wins. Either tag every request with a marker — a counter or the input value itself — record which tag is newest, and on arrival ignore any response whose tag is not current; or cancel the previous request before starting the next, so the superseded one never produces a result. Cancellation also frees the connection, but the caller must recognise a cancellation and not render it as a failure.

go deeper

for a junior

Remember the shape: change the input twice quickly, two requests are in flight, and the slower first one can land last and overwrite the newer data on screen.

for a middle

Explain both guards precisely — a per-request tag compared when the response arrives, and cancelling the superseded request — and why a liveness or loading flag fixes neither.

for a senior

Diagnose it from a vague report of 'wrong data sometimes', reproduce it by controlling response order in a test, and keep cancellation out of the error path so the fix does not create a flashing failure state.

for a principal

Decide where the rule lives and how it is enforced, so no new call site can forget it: a shared request layer that owns request identity, or a reviewed convention plus tests that resolve responses out of order.

## The race, step by step Take a detail panel whose request is keyed on a selected identifier. The user clicks item A, then quickly clicks item B. 1. The input becomes A. A request for A starts. 2. The input becomes B. A request for B starts; the request for A is still in flight. 3. The request for B answers first, because B's record is small or already warm on the server. 4. The panel renders B. Correct so far. 5. The request for A answers second and its handler writes A into the same state. 6. The panel now shows A while the selection says B. Nothing here is a bug in one framework. It is the consequence of two facts: **responses are not ordered**, and **every handler writes into the same slot**. The trigger does not need to be a click — the same shape appears with a text field typing faster than the server answers, with a refresh action fired while a scheduled reload is pending, and with any input that changes while work is outstanding. ## Guard one: ignore everything that is not the latest Give each request an identity and keep a record of which identity is current. - A monotonically increasing counter incremented at each start; the handler compares the value captured when its own request started against the current value. - A **stale flag** captured by the handler: each new start marks the previous flag stale, and a handler that finds its own flag stale returns without writing. - The input itself as the tag: on arrival, compare the input the response is for against the input as it is now, and write only on a match. All three are the same idea — the response must prove it is still wanted before it is allowed to write. The comparison must be about **request identity**, not about liveness. A check that only asks whether the owning instance still exists says nothing about which of two in-flight requests is newer, so it does not fix this race at all. ## Guard two: cancel the superseded request Before starting the next request, cancel the previous one. A cancelled request produces a cancellation outcome instead of a result, so nothing lands. Cancellation adds two obligations: - **Do not treat a cancellation as an error.** It is the expected outcome of superseding your own work. Routing it into the error path is what produces a failure banner that flashes on every rapid input change — a self-inflicted bug that looks like a server problem. - **Do not assume the work stops everywhere.** Cancelling detaches the client from the request; whether the server abandons the work it had already begun is not something the client can guarantee. For a read that hardly matters, and for a write it matters a great deal. ## The two guards compared | | Ignore the loser | Cancel the loser | |---|---|---| | Which response lands | only the newest | only the newest | | Work already in flight | finishes and is discarded | detached, and may still complete server-side | | Connections and bandwidth | held until the response arrives | released early | | Extra handling needed | a tag compared at arrival | a cancellation outcome kept out of the error path | | Works when the transport cannot cancel | yes | no, so a tag is still needed as the backstop | | Risk | wasted work under fast input | cancelling something another consumer wanted | In practice the tag is the correctness mechanism and cancellation is the efficiency mechanism, so a careful implementation does both: cancel to stop paying for superseded work, and still compare tags so that a response that slips through cannot write. ## Guards that look right and are not - **A liveness flag** ("is the owner still here?"). It prevents a write into something destroyed; it cannot tell an old response from a new one while the owner is very much alive. - **A single loading boolean.** It records that some request is pending, not which one, so the older response still passes the check. - **Assuming ordering.** Requests sent in order are not answered in order; server queueing, differing payload sizes, connection reuse and retries all reorder them. - **A timer alone.** Debouncing narrows the window; overlapping requests still occur after each pause. - **Writing into per-request state instead of one slot** helps only until two results must be reconciled into the same view, at which point the same choice reappears. ## Where the rule belongs The guard can live in the instance that requested the data, or in a shared request layer keyed by request identity, which resolves the race for every consumer at once and lets one response serve several. The mechanism is unchanged; what changes is who owns the tag and who is allowed to cancel. What is not acceptable is having no rule and relying on the server being consistently fast, because the bug that results is intermittent, environment-dependent, and reported as "the wrong data sometimes appears".

  • Why does a check that the owning instance still exists fail to prevent this overwrite?
    Because it answers the wrong question. Liveness tells you whether there is somewhere to write, not whether this response is the newest one wanted. During rapid input changes the owner stays alive the whole time, so every response passes the check and the last to arrive wins — which may be the oldest request.
  • If you cancel the previous request, do you still need a per-request tag?
    Usually yes. Cancellation can lose a race of its own — a response already in the handler queue may be processed anyway — and some transports or intermediaries cannot be cancelled at all. Keeping the tag makes correctness independent of cancellation actually taking effect, and cancellation then serves to stop paying for work nobody wants.
  • How do you reproduce this race reliably in a test?
    Control the responses instead of the timing: start the first request, start the second, resolve the second, then resolve the first, and assert the state still reflects the second. A fake transport that hands you the pending results makes the ordering explicit, so the test does not depend on artificial delays or on which machine runs it.
  • Two independent triggers in the same alive component both load the same slot. Does latest-wins still apply?
    Yes, and the tag has to be shared between them. If a manual refresh and a scheduled reload each keep their own counter, each will consider its own response current. One counter or one flag per state slot — incremented by whichever trigger starts a request — is what makes 'newest' well defined.

saying these in an interview costs you the question

  • Assumes responses arrive in the order their requests were sent
  • Guards with a still-alive check, which says nothing about which request is newest
  • Believes a single loading boolean blocks the older response from writing
  • Renders a cancelled request as a failed request, flashing an error on fast input
  • Says debouncing alone removes the race rather than narrowing it
  • Thinks cancelling on the client guarantees the server stopped the work