Out-of-order responses and stale search results.

The box says "paris" and the dropdown shows results for "pa". It is the most common race condition on the web, and it only shows up on real networks.

Illustration of the failure GenClass targets, drawn for this page with the report line shortened; the real line format is on the docs. Without GenClass the app renders "pa" results under a search box that says "paris". In observe mode, the default, GenClass reports this and delivers the response unchanged; in guard mode, opt-in, it can discard the write when its gate clears.

What goes wrong

Every keystroke sends a request. HTTP responses are not guaranteed to arrive in the order they were sent: one may hit a cold cache, a slower server or a congested connection. In the trace above, the request for "pa" takes 1,240 ms and lands after the results for "paris" are already on screen. If the app writes whatever arrives, the screen goes backwards.

Debounce is not enough

Debouncing input to 200–300 ms reduces the number of requests, which is good, but it does not order them. Two debounced requests can still race. Debounce for load; use one of the fixes below for correctness.

Cancel the old request, or compare before writing

let controller;
async function search(q) {
  controller?.abort();
  controller = new AbortController();
  const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`, { signal: controller.signal });
  const data = await res.json();
  if (q === input.value) render(data);   // only render results for what the box says now
}

Aborting saves bandwidth; comparing against the current input value is the safety check that actually guarantees correctness. Do both.

The switchMap pattern

In RxJS, switchMap unsubscribes from the previous inner request whenever a new value arrives, which encodes "only the latest matters" directly:

fromEvent(input, "input").pipe(
  map((e) => e.target.value),
  debounceTime(200),
  distinctUntilChanged(),
  switchMap((q) => fetchResults(q)),
).subscribe(render);

Catch the ones you missed in production

Most apps have more than one search box, and filters, tabs and pagination fail the same way. GenClass detects a response that would overwrite newer data across every fetch in your app and, in guard mode, discards it before render. At the default setting it acts rarely; the benchmarks show how often.

npm install @genclass/runtime
See it live: the live demo types into a search box on an out-of-order network, with the real runtime deciding on your device.

Related: race conditions in React useEffect.