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.
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
- cooperative cancellation: Signal obsolete work to stop where the API cooperates.
- debounce: Choose when to start work; response ordering remains a separate rule.
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.