A race condition is a bug where a program produces the wrong result because two or more operations run at the same time and the outcome depends on which one happens to finish first. The code looks correct when read top to bottom, but under concurrency the order is not guaranteed, so the answer changes from run to run. Race conditions are notorious because they are intermittent: they may pass a thousand tests and then fail in production under load. In 2026, with async code and multi-core machines everywhere, they remain one of the most common and frustrating classes of bug.
What a race condition actually is
The word race is literal. Two pieces of code are racing toward the same shared resource, and whichever gets there first determines the result. Because timing is not under your control, the result is unpredictable.
The shared resource is the key. If two operations never touch the same data, there is no race. The danger appears the moment two concurrent actors read and write the same variable, file, row, or counter, which is common when a server handles many requests at once.
The classic read-modify-write problem
The most common race is a read-modify-write on shared state. Imagine two requests both incrementing the same counter.
-- Two tasks run this at the same time on a shared balance
let balance = 100;
const current = balance; // both read 100
balance = current - 50; // both write 50, one update is lost
Both read 100. Both subtract 50. Both write 50. Two withdrawals happened, but the balance only dropped once. One update was silently lost. The code is logically fine; the concurrency is what broke it.
| Step |
Actor A |
Actor B |
Shared balance |
| Read |
sees 100 |
sees 100 |
100 |
| Modify |
computes 50 |
computes 50 |
100 |
| Write |
writes 50 |
writes 50 |
50 (wrong) |
How to prevent race conditions
| Technique |
What it does |
Best for |
| Mutex / lock |
Lets only one actor into a critical section |
General shared-memory access |
| Atomic operation |
Makes read-modify-write one indivisible step |
Counters and flags |
| Database transaction |
Groups reads and writes safely |
Shared rows in a database |
| Avoid shared state |
Give each actor its own data |
The simplest fix when possible |
A lock makes the critical section, the few lines that must not be interrupted, run for only one actor at a time. An atomic operation does the same for a single step in hardware or the language runtime. In a database, a transaction with the right isolation level or a conditional update prevents the lost write. The cleanest fix of all is to avoid sharing mutable state in the first place.
How to find and fix one
- Identify the shared resource. Find the variable, row, or file that multiple actors touch.
- Find the critical section. Locate the read-then-write sequence that must be atomic.
- Protect it. Wrap it in a lock, replace it with an atomic operation, or use a transaction.
- Keep the protected region small. Holding a lock too long hurts performance and can cause deadlocks.
- Test under load. Races hide at low concurrency. Stress the code to expose them.
What to skip
- Adding sleeps to make it pass. A delay only changes the timing; the race is still there waiting for a worse moment.
- Locking everything. Coarse locks kill concurrency and can create deadlocks. Protect only the true critical section.
- Assuming a single thread is safe. Async code interleaves on one thread too; awaiting between a read and a write reopens the same gap.
- Ignoring intermittent failures. A test that fails one time in fifty is often a race, not a fluke. Investigate it.
FAQ
Do race conditions only happen with threads?
No. Any concurrency can cause them: threads, async tasks on one thread, separate processes, or multiple servers writing to the same database row.
Why are race conditions so hard to debug?
They depend on timing, so they appear intermittently and often vanish under a debugger, which changes the timing. Reproducing them reliably is the hard part.
What is a critical section?
The block of code that accesses shared state and must run without interruption from another actor. Protecting it is how you eliminate the race.
Is a deadlock the same as a race condition?
No. A race condition is an unpredictable result from timing. A deadlock is when actors wait on each other forever. Overusing locks to fix races can cause deadlocks.
Where to go next
See what a deadlock is in 2026, what async await is in 2026, and how to fix a bug in 2026.