# race condition: a worked example

Canonical page: https://vibe-coding-101.brighteng.org/c/race-condition/#before-and-after

## The problem

You search for 'ca', then 'cat'. The cat request finishes first, but the slower ca request arrives afterward. The rule is that only the latest requested search may update the results, even when a query is repeated.

## Prompt to paste

Reproduce this race with overlapping operations. Protect the shared invariant and verify both completion orders, including a failed operation.

## Before

Every completion writes to the same display. The network completion order decides what the reader sees, so an obsolete response can overwrite the current result.

## After

Assign an increasing generation to each requested search. Publish a completion only if its generation is still current. This comparison registers each requested search immediately. In a debounced UI, invalidate the previous generation on each input change, before waiting to send the next request.

## Run the check

Save the following code as race-condition.mjs and run `node --test race-condition.mjs` with Node.js 22.12 or later.

```js
import test from 'node:test';
import assert from 'node:assert/strict';

function before(publish) {
  return (request) => request.then(publish);
}

function after(publish) {
  let generation = 0;
  return async (request) => {
    const mine = ++generation;
    const result = await request;
    if (mine === generation) publish(result);
  };
}

test('an older completion overwrites current results', async () => {
  const shown = [];
  let finishOld;
  const old = new Promise((resolve) => { finishOld = resolve; });
  const search = before((value) => shown.push(value));
  const first = search(old);
  await search(Promise.resolve('cat'));
  finishOld('ca');
  await first;
  assert.deepEqual(shown, ['cat', 'ca']);
});

test('the current generation wins in both orders, including a failure', async () => {
  for (const oldFirst of [true, false]) {
    const shown = [];
    let finishOld, finishNew;
    const old = new Promise((resolve) => { finishOld = resolve; });
    const latest = new Promise((resolve) => { finishNew = resolve; });
    const search = after((value) => shown.push(value));
    const first = search(old);
    const second = search(latest);
    if (oldFirst) { finishOld('ca'); await first; finishNew('cat'); }
    else { finishNew('cat'); await second; finishOld('ca'); }
    await Promise.all([first, second]);
    assert.deepEqual(shown, ['cat']);
  }
  const shown = [];
  const search = after((value) => shown.push(value));
  let finishOld;
  const first = search(new Promise((resolve) => { finishOld = resolve; }));
  await assert.rejects(search(Promise.reject(new Error('offline'))), /offline/);
  finishOld('obsolete');
  await first;
  assert.deepEqual(shown, []);
});
```

## Expected result

The unguarded version displays cat and then ca. The guarded version publishes only cat in either completion order. If the latest request fails, an older success still cannot become current; the caller receives the error and must show the current query's error state.

## Limits

This guard protects one display owner; it does not stop network work, make database writes atomic, or make side effects safe to retry. Handle errors for the current generation and invalidate it when leaving the view. Comparing query strings alone is insufficient when the same query is requested twice. The interactive example compares two already-started requests; connecting this guard to an actual input is a separate integration step.

## Sources

- [React: Fetching data in Effects](https://react.dev/reference/react/useEffect#fetching-data-with-effects): Documents responses arriving in a different order and ignoring obsolete completions. The generation example here is framework-independent and original.

Sources checked: 2026-10-11. These are original illustrative examples with automated checks. Human editorial approval is not recorded. Policy: https://vibe-coding-101.brighteng.org/editorial/
