Some of the hardest bugs in software are not about wrong logic but wrong timing. A race condition happens when the correctness of your code depends on the order in which concurrent operations happen to run — and sometimes that order goes wrong. These bugs are notorious for vanishing the moment you try to catch them.
A classic example
Imagine two requests both incrementing a counter that holds 100. Each reads the value (100), adds one (101), and writes it back. If they run one after another, the result is 102. But if they interleave — both read 100 before either writes — they both write 101, and one increment is silently lost. Nothing is logically wrong with the code; the bug lives entirely in the timing.
Why they are so hard to catch
Race conditions are intermittent by nature. They only manifest when operations interleave in a specific, unlucky way, which may happen rarely and almost never under a debugger or in a test that runs operations one at a time. A race that corrupts data once in ten thousand times in production can be effectively impossible to reproduce on demand — which is what makes them so feared.
The root cause: unguarded shared state
Every race condition comes down to the same thing: multiple operations accessing shared mutable state without coordination, where at least one of them writes. Remove any of those ingredients — make the state immutable, avoid sharing it, or coordinate access — and the race disappears. That framing points directly at the fixes.
How to prevent them
- Locks — ensure only one operation touches the shared state at a time, so they cannot interleave.
- Atomic operations — use operations the system guarantees happen indivisibly, like an atomic increment or a database UPDATE that computes the new value.
- Avoid shared mutable state — immutable data and message passing sidestep the problem entirely.
- Database constraints and transactions — let the database enforce correctness under concurrency.
- A race condition is a bug where correctness depends on timing of concurrent operations.
- They are intermittent and notoriously hard to reproduce.
- The root cause is unguarded shared mutable state with concurrent writes.
- Prevent with locks, atomic operations, immutability, or database transactions.
Frequently asked questions
Do race conditions only happen with threads?
No. They occur anywhere operations run concurrently on shared state — multiple servers, async tasks, separate requests hitting the same database row, not just multithreading.
How do I fix a "lost update" on a counter?
Use an atomic increment — either a database operation that adds in a single statement, or an atomic primitive — rather than reading, adding, and writing back separately.