Glossary / Race conditions
Race conditions
A race condition occurs when correctness depends on the relative timing or ordering of operations that can complete in different orders. JavaScript’s run-to-completion rule prevents two ordinary jobs on one agent from executing statements simultaneously, but asynchronous operations can still race: requests return out of order, state changes between awaits, callbacks re-enter code, or multiple workers access shared memory.
Latest-request-wins example
let requestId = 0;
async function search(query) {
const id = ++requestId;
const result = await fetchResults(query);
if (id === requestId) render(result); // ignore stale response
}
Other strategies include aborting obsolete requests, serializing operations, version checks, mutexes/queues, optimistic concurrency tokens, and idempotent updates. Choose by semantics: “latest wins” is not correct for payment commits or append-only events. A check before await may be stale after resumption; re-check invariants at commit time.
SharedArrayBuffer introduces true cross-agent concurrent access and memory-order concerns; use Atomics and carefully designed synchronization. UI races still matter even without shared memory because asynchronous completions can reorder. Tests should control completion order with deferred Promises and test both winner/loser permutations.
Interview questions
- Can single-threaded JS have race conditions? Yes, asynchronous completion order can affect shared state.
- What is stale-response protection? Version/request ID or cancellation checks that prevent outdated results from committing.
- Why revalidate after await? Other jobs may have changed relevant state while the function was suspended.
- How test races? Use controlled deferred operations to force alternate completion orders.