Race conditions in the UI: when the old answer arrives last
Why a search box shows results for an older query, and how to fix it: last-request-wins with abort or request IDs, debounce races, optimistic updates with rollback, and double-submit guards.
Comes up in: Coding, Machine coding, React
Learn#
Fatima types “shoes” into a shopping site. The page shows results for “s”. Each keystroke sent a request, and networks do not promise order: the slow “s” request arrived last and replaced the right results.
That is a race condition: the result depends on which async task finishes first. The late answer is a stale response.
const delays = { s: 150, sh: 50, shoes: 100 };
const search = (q) => new Promise((r) => setTimeout(() => r(`results for "${q}"`), delays[q]));
let shown = "";
const buggy = (q) => search(q).then((res) => { shown = res; });
await Promise.all(["s", "sh", "shoes"].map(buggy));
console.log("buggy:", shown);
let latestId = 0;
const fixed = async (q) => {
const id = ++latestId;
const res = await search(q);
if (id === latestId) shown = res;
};
await Promise.all(["s", "sh", "shoes"].map(fixed));
console.log("fixed:", shown);Output (verified)
buggy: results for "s" fixed: results for "shoes"
Network
- s (150 ms) …
- sh (50 ms) …
- shoes (100 ms) …
State
- latestId = 3
Page
empty
The user types s, sh, shoes. Three requests start, in that order.
Read the steps as text
- The user types s, sh, shoes. Three requests start, in that order.
- "sh" answers first. Its id is 2, not 3, so it is ignored.
- "shoes" answers. Its id matches latestId, so it is shown.
- "s" answers last. Without the id check it would replace the right results with the wrong ones.
The rule is “last request wins”. Check an id before showing results, or better, abort the old request (JS.11.04) so it never arrives.
Predict#
Predict first
You add a 300 ms debounce to the search box. Is the stale-results bug gone?
Debounce sends fewer requests, but a request can still take longer than 300 ms. If the user pauses, types again, and the first reply is slow, the old answer can still arrive last. Debounce is tempting because the bug becomes rarer, which makes it harder to find.
Interview angle#
Say it like this — about 30 seconds
“The search box shows old results because responses can arrive out of order: a slow early request lands after a fast later one. I make the last request win. I abort the previous request with an AbortController, and I also check a request id or the current query before setting state. Debounce reduces requests but does not fix the race. For optimistic updates I keep the previous state for rollback, and I block double submits while a request is pending.”
Follow-up Your search box sometimes shows results for an older query. Why, and how do you fix it?
Follow-up How do you roll back an optimistic update?
Follow-up How do you stop a double payment from a double click?
Practice#
On your own
Write two guards: latestOnly for search and singleFlight for a submit button.
Done when
- With
latestOnly, an older call rejects with an AbortError and its signal is aborted. - Only the newest call resolves.
- With
singleFlight, calls during a pending request share one promise and one call tofn. - After it settles, the next call runs
fnagain, also after a failure.
Remember#
Try to answer each card before you open it. Then add them to your reviews.
Why can a search box show results for an older query?
Responses can arrive out of order; a slow old one lands last.
Does debounce fix out-of-order responses?
No. It sends fewer requests, but they can still overlap.
What is an optimistic update?
Showing the change at once, before the server confirms, with a rollback on failure.
Name two ways to make the last request win.
Abort the previous request, or ignore responses whose id is not the latest.
Go deeper
Four layers. Stop at the one your interview needs; each ends with a quick check.
The core ideaEvery round
Store the latest id or query. When a reply arrives, compare: if it is not the latest, drop it. This works even with APIs that cannot be aborted.
Check yourself: Which value do you compare before showing a search result?
The request's id or query against the latest one.
How it works underneathSenior rounds
Aborting stops the download, but a reply may already be in the response queue when you abort. The id check covers that gap. In React, an effect’s cleanup can abort and set an ignore flag for the same reason.
Check yourself: Why do aborting and the id check work well together?
Abort saves bandwidth; the id check catches replies that finish before the abort lands.
Edge cases and trapsWhere people slip
Check yourself: What should happen to an optimistic like if the request fails?
Restore the old count and show an error message.
In productionStaff-level answers
Idempotency-Key header, so a retried or doubled request is run once on the server Source: IETF draft: The Idempotency-Key HTTP Header Field.Check yourself: What does an idempotency key do?
Lets the server recognize a repeated request and not run it twice.
Finished the five steps? Mark it, and its cards join your reviews.
Last checked 2026-10-31. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.
Interview questions this answers
- Your search box sometimes shows results for an older query. Why, and how do you fix it?