Type react into a search box. Then quickly change it to react hooks.
The first request takes 900 milliseconds. The second takes 200. The newer result arrives first, but the older result arrives later and overwrites it.
Both requests succeeded. The screen still shows the wrong result.
This is a useful interview debugging exercise because the failure depends on completion order, not on a syntax error or a failed HTTP request.
Reproduce the order before changing the code
Log the query when each request starts and finishes. Deliberately delay the first response. Check which result is allowed to update the UI.
The rule you want is simple: only the latest requested search may display a result or an error.
Give each request an identity
Here is a small illustration. renderResults and renderError stand for your application's existing UI functions. The endpoint and response shape are examples.
let latestRequest = 0;
async function search(query) {
const request = ++latestRequest;
try {
const response = await fetch(
`/api/search?q=${encodeURIComponent(query)}`
);
if (!response.ok) {
throw new Error(`Search failed: ${response.status}`);
}
const results = await response.json();
if (request !== latestRequest) return;
renderResults(results);
} catch (error) {
if (request !== latestRequest) return;
renderError(error);
}
}
The counter changes when a search starts. A response carries its own request number. An older response cannot pass the final comparison after a newer search has begun.
The error path needs the same check. Otherwise an old request can replace a successful new result with an irrelevant error.
This deliberately makes “latest request wins” the policy. If the newest search fails, the example displays its error. A product might instead preserve the last successful result, but that needs an explicit rule.
Cancellation and debouncing solve different parts
An AbortController can cancel an earlier fetch and consumption of its response body. That can save unnecessary client work. It does not promise that a server never received or processed the request.
Debouncing reduces how often you start a search. It does not, by itself, establish which response may update the screen. Once requests overlap, you still need a policy for stale results.
If this code belongs to a component, keep its request identity within that component's lifetime rather than sharing one global counter across unrelated search boxes. Handle component cleanup too. A framework data-fetching layer may already provide these controls.
Practise the explanation aloud
Try these follow-up questions:
- Why can two successful requests produce an incorrect UI?
- Why does the error branch need the same identity check?
- What changes when the newest request fails?
- What does cancellation save, and what does it leave uncertain?
Explain the sequence first, then the invariant your fix preserves. That gives a reviewer something concrete to check.
For more rehearsal prompts, I created a free ten-question software engineering interview sample. If the answer style is useful, the full Software Engineering Interview Playbook is £25.
References: React's data-fetching and race-condition guidance, MDN on aborting a request, and MDN on checking fetch responses.
Top comments (0)