Engrain by ByteRush

JS.11.05 Mid-level· 11 minCore — asked often

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"
Last request wins, with a request idStep 1 of 4

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
    1. The user types s, sh, shoes. Three requests start, in that order.
    2. "sh" answers first. Its id is 2, not 3, so it is ignored.
    3. "shoes" answers. Its id matches latestId, so it is shown.
    4. "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?

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

    Stays on your device. Nothing is uploaded.
    Follow-up Your search box sometimes shows results for an older query. Why, and how do you fix it?
    Responses arrive out of order. Abort the old request and ignore any response whose id or query is not the latest.
    Follow-up How do you roll back an optimistic update?
    Save the previous value, update the UI at once, and restore the saved value if the request fails, then tell the user.
    Follow-up How do you stop a double payment from a double click?
    Disable the button while pending, share one in-flight request, and send an idempotency key so the server ignores repeats.

    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 to fn.
    • After it settles, the next call runs fn again, also after a failure.
    Your solution
    Ctrl/⌘ + Enter

    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
    Debounce plus slow networks still races; combine it with last-request-wins.
    An optimistic update changes the UI before the server answers. Keep the previous state, roll back on failure, and handle a second change made while the first is pending.
    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

    For payments and orders, disable the button while pending and send an Idempotency-Key header, so a retried or doubled request is run once on the server Source: IETF draft: The Idempotency-Key HTTP Header Field.
    TanStack Query cancels and de-duplicates queries for you Source: TanStack Query: Query cancellation.

    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?