When two threads try to use the same variable without taking turns.
A Data Race occurs when two threads access the exact same memory location concurrently, and at least one of them is writing to it. Because threads can be paused by the OS at any given microsecond, the final result depends entirely on the unpredictable timing (the "race") of the threads. This leads to Heisenbugs—bugs that disappear when you try to observe them.
The solution to a data race is Mutual Exclusion (Mutex). You place a lock around the shared variable. Before a thread can read or write the variable, it must acquire the lock. If another thread has the lock, it must wait its turn.
# Go example: Fixing a data race with a Mutex
import "sync"
var balance int = 100
var mu sync.Mutex // The Lock
func withdraw(amount int) {
mu.Lock() // 1. Grab the lock
balance -= amount // 2. Safely modify
mu.Unlock() // 3. Let others have a turn
}
func deposit(amount int) {
mu.Lock()
balance += amount
mu.Unlock()
}
Locks eliminate data races, but they introduce contention. If 1,000 threads all need to update `balance`, they must form a single-file line. Your beautiful multi-core CPU is suddenly executing code sequentially, destroying performance.
print(balance) to debug it, the bug might vanish! I/O operations (like printing) often involve hidden locks or take enough time to alter the thread timing, "fixing" the race temporarily. Always use tools like Go's -race detector instead.