What is a race condition? The lost update explained
By Unlearnt
A race condition is a bug where the result depends on the timing of operations that run at the same time. Because that timing is not controlled, the program can give the right answer on one run and the wrong one on another. The classic case is the lost update, where two operations read the same value before either writes.
When timing changes the answer
When two operations share data and run concurrently, the order in which their individual steps interleave can change the outcome. If a step reads a value, does some work, then writes it back, another operation can slip in between the read and the write.
Because the scheduler decides that interleaving, the result is not deterministic. That is what makes race conditions hard: the code looks correct and often works, then fails intermittently under load when the unlucky interleaving occurs.
A worked example: the lost update
Two requests each deposit £50 into an account that starts at £100. Both read the balance as £100 before either writes. Request A computes £150 and writes it; request B, still holding its £100, also computes £150 and writes it.
The balance ends at £150, not £200. One deposit was lost because both decisions used the same stale read. Nothing was wrong with either request alone; the fault was the unguarded overlap.
Two £50 deposits, but the balance ends at £150, not £200.
Step
Request A
Request B
Read balance
reads £100
Read balance
reads £100
Add £50, write
writes £150
Add £50, write
writes £150
Final balance £150, one £50 deposit lost.
Fixing it: make the step atomic
The fix is to stop the read and write being separable. An atomic update does it in one indivisible step, for example a database statement that adds to the balance directly rather than reading it into the application first. The database then applies both deposits in turn.
Other tools enforce the same thing: a lock so only one operation holds the value at a time, a compare-and-set that writes only if the value has not changed since it was read, a unique constraint, or a serializable isolation level that makes concurrent transactions behave as if run one after another.
Misconception: it is rare, so it is fine
Race conditions are probabilistic, not impossible. They become more likely as traffic rises, which is exactly when the cost is highest, and they can be very hard to reproduce because they depend on precise timing. Passing tests do not prove the overlap cannot happen.
They can also be security-relevant. A check followed by a use, with a gap in between, can let state change between the two steps, a pattern known as time-of-check to time-of-use. Treat shared mutable state under concurrency as needing a deliberate guard, not luck.
Check your understanding
Optional: apply the idea to a new scenario.
An online shop has one unit of an item in stock. Two customers check out at the same moment; both requests read the stock as 1, both decide the sale is allowed, and both write the stock down to 0, so the single item is sold twice. Why did this happen, and how would you change the code so it cannot oversell?
Answer with the tutor →Free to start, no account needed.