Tacnode™
Glossary
Data Consistency

What Is Race Condition?

A race condition is a defect in which a system's correctness depends on the relative timing or ordering of concurrent operations. It arises when two or more processes read and modify shared state without sufficient coordination, so that certain interleavings produce a result no correct serial execution would produce. Race conditions occur at every layer of computing — between CPU threads, between database transactions, and between services in a distributed system.

Updated

What is a race condition?

The term comes from electronics, where two signals "race" through a circuit and the output depends on which arrives first. In software the shape is the same: an outcome that should be deterministic becomes a function of scheduling luck.

Most race conditions fall into two classes. Read-modify-write races: two actors read a value, compute a new value from it, and write back — the second write silently discards the first actor's update. Check-then-act races (also called TOCTOU, time-of-check to time-of-use): an actor validates a condition, then acts on it, but the condition changes between the check and the act.

What makes race conditions notorious is that they are timing-dependent: the code is correct under almost all interleavings and fails only under the specific overlap that testing rarely reproduces. They surface under production concurrency, and they fail silently — producing a wrong result rather than an error.

How a race condition works

The canonical example is a concurrent withdrawal. An account holds $100; two $80 withdrawals arrive at the same time:

text
Request A                       Request B
read balance        → 100
                                read balance        → 100
check 80 ≤ 100      → pass
                                check 80 ≤ 100      → pass
write balance = 20
                                write balance = 20

Both withdrawals succeed: $160 leaves a $100 account, and the final balance ($20) doesn't even record it. Each request was individually correct; the interleaving was not.

The standard database fixes make the check and the act atomic:

sql
UPDATE accounts
SET balance = balance - 80
WHERE id = 7 AND balance >= 80;
-- 0 rows updated ⇒ insufficient funds; reject the withdrawal

The database serializes the two updates on the row lock, so the second re-evaluates its predicate against the first's result and fails. Alternatives: pessimistic locking (SELECT ... FOR UPDATE), optimistic locking with a version column, or running at SERIALIZABLE isolation and retrying aborts.

Why race conditions matter in real-time systems

When the contested state is a single stored row, the fix above is sufficient — the database's atomicity and row locking close the race. The harder class in automated decisioning is when the decision depends on derived state: a velocity count over the last ten minutes, an exposure aggregate across accounts, context joined from several systems. There is no single row to lock. The derived value is computed somewhere — a stream job, a cache, a materialized aggregate — and the race moves to the gap between an event occurring and the derived value reflecting it.

Under concurrency this becomes systematic: a burst of simultaneous transactions each reads an aggregate that includes none of the others, and each decision passes a check that the full picture would fail. This is the same check-then-act structure as the withdrawal example, replayed at architecture scale — analyzed further in context under concurrency and in real-time fraud detection architecture, where concurrent low-value probes exploit exactly this window.

FAQ

Related terms

Further reading