Race conditions.
Two people grabbing the last cookie at once because each looked, saw it there, and reached.
Two people share a kitchen with one cookie left. Each glances at the jar, sees a cookie, and reaches in. Because they checked and acted separately, both think the cookie is theirs — and now the count is wrong, or someone is upset.
A race condition is that bug in code. Two threads read and update the same data at almost the same time, and the result depends on the exact, unpredictable order they happen to run in. Most of the time it works; once in a while the steps interleave badly and the data ends up wrong.
- Mine!1
Two people both reach for the one last seat at the very same moment.
- 2
In code: two threads both read the same value, a balance of 100.
- 3
Each subtracts 30 and writes 70 back — so one withdrawal silently disappears.
- After you.4
A lock fixes it: only one thread enters at a time, the other waits its turn.
- You go first… no, you.5
But locks have their own trap — deadlock, where each thread waits for the other forever.
- 6
The cleanest fix is to not share at all: give each thread its own copy.
Why they are so nasty
The bug only appears when the timing lines up just wrong, which may be one run in a million. So it passes every test, works in development, and then misbehaves rarely and unpredictably in production. The same code gives different results on different runs, which makes it maddening to reproduce and debug.
How you prevent them
The core fix is to make the critical section — the read-modify-write on shared data — happen as one indivisible step. A mutex (lock) lets only one thread in at a time; atomic operations do small updates as a single uninterruptible instruction. The bigger discipline is to avoid sharing mutable state at all where you can, by giving each thread its own data or passing messages instead.
Locks bring their own hazard: if two threads each hold one lock and wait for the other, neither can proceed — a deadlock.