A race condition is a bug in which the correctness of a program depends on the relative timing or interleaving of operations that are not under the programmer’s control. Two or more concurrent activities, threads, processes, or hardware events, “race” to access a shared resource, and whether the program produces the right answer depends on which one happens to win. Because the timing varies from run to run, the program may work thousands of times and then fail unpredictably under load or on a different machine.
The classic example is a lost update. Two threads each read a counter holding the value 5, each add one, and each write 6 back. The counter should have ended at 7, but one increment was silently lost because the read-modify-write sequence was not performed as an indivisible unit. Nothing in either thread is wrong in isolation; the bug lives entirely in their unsynchronized interaction.
A particularly important subclass of race is the data race, which modern language standards define precisely so that compilers and programmers can reason about it. The ISO C standard committee draft N1570 specifies that the execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other, and that any such data race results in undefined behavior. Defining the hazard this sharply is what lets a memory model promise sequential consistency for programs that are free of data races, while leaving programs that contain them with no guarantees at all.
Another widely seen pattern is the time-of-check-to-time-of-use bug, or TOCTOU. A program checks a condition, such as “does this file exist and is the user allowed to open it,” and then acts on that check a moment later. In the gap between the check and the use, another process can change the world, swapping the file for a symbolic link to a sensitive target. TOCTOU races are a recurring source of security vulnerabilities precisely because the window between check and use is hard to see and harder to close.
Race conditions are notoriously difficult to debug because the act of observing them often changes their timing. Adding a print statement or attaching a debugger can slow one thread just enough that the race no longer occurs, producing a bug that vanishes when watched. This frustrating property has its own name, the heisenbug, and it is one reason concurrency bugs are dreaded out of proportion to how often they fire.
The defenses are structural rather than incidental. Locks, atomic operations, and message passing serialize access to shared state, while ownership-based type systems can forbid unsynchronized sharing at compile time. The common thread is removing the program’s dependence on uncontrolled timing, so that correctness no longer rests on winning a race.