Skip to catalogue

race condition

An outcome depends on the timing of concurrent operations.

Use this when Two overlapping operations can update the same state.

without the term

“This bug only happens sometimes, usually when two requests come in at the same moment. Each one works fine on its own. Can you figure out what's going on?”

with the term · How do you ask a model for race condition?

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

XBlueskyLinkedIn

What is race condition?

Two operations overlap, such as a check followed by a write, and the result depends on which finishes first. Each step can be correct on its own while the combination is wrong.

Why does race condition matter when vibe coding?

A sequential test can miss an unsafe check-then-write sequence.

How do you do race condition?

Identify the shared invariant and exercise competing operations with controlled ordering.

What goes wrong with race condition?

Fixing the visible order while leaving the underlying write unprotected.

Before and after

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.

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.

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

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.

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

Check the change

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.

Save the runnable comparison, then run node --test race-condition.mjs with Node.js 22.12 or later.

Download the complete teaching example: the bug, prompt, runnable code, expected result, limits, and sources, linked back to this page.

Show the assertions
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, []);
});

When this is not enough

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.

The next decision

Sources and verification

  • React: Fetching data in Effects

    Documents responses arriving in a different order and ignoring obsolete completions. The generation example here is framework-independent and original.

Sources checked . These are illustrative examples with automated checks. How we check content.

Did this help with your task?

After trying the prompt or check, tell us what happened. This optional response records only your choice and this term.

Choose between related terms

adjacent

Expanded example updated .

Historical short-entry review

report a correction