Understanding Race Conditions Through Everyday Life: From Coffee Machines to Bank Withdrawals

How race conditions cause chaos in concurrent systems — and how locks and idempotency keep things sane.
Race conditions are among the trickiest problems in concurrent programming: when multiple processes access shared resources simultaneously, tiny differences in execution order can cause everything from data corruption to total system freezes. Through analogies like single-button coffee machines, joint bank accounts, and cars stuck at a roundabout, this article distinguishes livelocks (processes run but never finish) from deadlocks (processes can't move at all), and introduces idempotency and mutual exclusion locks as the core solutions. Lock correctness ultimately depends on atomic CPU instructions, at the cost of reduced concurrency. Race conditions aren't theoretical — Ethernet collision avoidance and Wi-Fi time-slot contention are real-world examples, and these bugs typically only emerge under high-concurrency production loads, making them notoriously hard to reproduce and debug.
When Two Things Happen at Once, Computers Fall Apart
One of the trickiest problems in computing is trying to do multiple things at exactly the same moment. These problems are collectively known as Race Conditions. Computerphile uses a series of relatable, everyday examples to make this abstract engineering challenge feel intuitive and tangible.
The most classic analogy is two people at a dinner table who start talking at the same time. Each politely gestures for the other to go first — and then both start talking again simultaneously. Humans eventually resolve this through randomness. But computers are different: if you program two machines with the same logic of "wait when you hear someone else talking," they'll likely wait for identical durations, falling into an endless loop of talking, backing off, and talking again.
Even with exponential backoff (waiting 1 second, then 2, then 4…), if all devices share synchronized clocks, they'll never manage to stagger themselves. You might think, "just wait a random amount of time" — but computers are fundamentally deterministic and might always land on the same "random" number. In practice, random number generators typically sample from environmental noise, which is why this particular problem doesn't usually occur in the real world.
The Single-Button Coffee Machine Trap: Introducing Idempotency
Matt shared a frustrating everyday example: a pod coffee machine with just one button that handles both starting and stopping. You place a randomly sized cup, press to start, and watch the coffee flow. As it's about to overflow, you rush to press stop — but nine times out of ten, the machine was just about to stop itself anyway, so your press actually restarts it, and the coffee spills right over the edge.
This is a textbook race condition: the machine wants to stop, you want to stop, but there's only one switch controlling two opposing states. The simplest fix is a design choice — add a dedicated stop button.

This introduces a key concept: Idempotency. It means that executing the same operation multiple times produces the same result as executing it once. If the stop button were idempotent, pressing it any number of times while the machine is already stopped would leave the system's state unchanged. Making operations idempotent is an effective way to eliminate certain race conditions.
Buying Milk and Bank Withdrawals: From Harmless to Catastrophic
Roommates simultaneously notice the fridge is out of milk, one heads to Tesco and the other to Sainsbury's, and they return with two cartons — this is a harmless race condition. The worst outcome is a slightly crowded fridge.
But in a banking scenario, the consequences become serious. Imagine an ATM withdrawal function: it checks whether the account balance is ≥ the withdrawal amount, dispenses cash if so, then deducts the amount from the balance. Seems airtight.
Now imagine you and your spouse share a joint account with just £100 left. You both simultaneously attempt to withdraw that last £100 at two different ATMs. Both machines run the same code, both determine "balance 100 ≥ withdrawal 100," and both dispense cash. Then the trouble starts during deduction: if both machines read 100 and each subtracts 100, the account might magically end up at £0 (£100 conjured from thin air), or the account might go £100 into debt.
This is the dangerous form that race conditions take in financial systems. Of course, real banks would never write code this way — please don't try this at home.
The standard defenses against this kind of race condition in banking systems are database transactions and atomizing the "read-modify-write" sequence. Real banking systems use row-level locking or optimistic locking: either lock the balance at read time (pessimistic locking), or verify at write time that the balance still matches what was read (optimistic locking), rolling back and retrying if it was changed in between. At a deeper level, this is guaranteed by a database's ACID properties — specifically Isolation, which handles concurrent transactions interfering with each other. This is why even careless application-layer code is usually protected against the "double withdrawal" scenario by mainstream relational databases at their default isolation level. That said, in higher-concurrency situations, different isolation levels can still introduce subtle race conditions like dirty reads and phantom reads.
Deadlocks, Livelocks, and the "Stealing Nests" Game
Not every race condition causes a permanent freeze. In the examples above, one party eventually "wins." But when computer programs do get stuck, there are two specific named conditions.
Livelock: Both parties are running, both are taking action, but neither can ever complete the task. The infinite "after you — no, after you" at the dinner table is a livelock. Matt gives a brilliant example — the children's game "Rob the Nest": three children stand at the vertices of a triangle, each guarding a traffic cone, with the goal of stealing the other two cones to collect all three at their own base. The rule is you can't defend, only run and steal. The result? Nobody ever wins. You go steal someone else's cone, and yours gets stolen while you're away. Everyone is constantly "working," the CPU is spinning furiously, but the task never completes.

Deadlock: Processes are completely unable to make progress. Three cars arrive simultaneously at a three-way roundabout (or a stop-sign intersection in the US), where the rule is "yield to the right." All three cars wait for the car on their right to go first — and nobody moves. A car in a parking lot wants to back out, a car behind it has pulled up, and another car around the corner wants to pull in — completely frozen. That's the definition of deadlock.

The classic academic model is the Dining Philosophers Problem: robots seated around a table, with only one chopstick between each pair of neighbors. Everyone must pick up their left chopstick before their right to eat — and everyone ends up stuck. The fix is to break the symmetry: have one robot pick up their right chopstick first, and the entire chain can flow.
The Dining Philosophers Problem was proposed by computer scientist Dijkstra in 1965 and remains one of the most classic thought experiments in concurrent control. Beyond breaking symmetry, the field also proposes resource ordering: assign a number to every resource and require every process to request resources in ascending order. This makes circular waiting impossible — because a circular wait would require some process to request a lower-numbered resource while already holding a higher-numbered one, which the rule explicitly forbids. Deadlock requires four conditions to hold simultaneously (the Coffman conditions): mutual exclusion, hold and wait, no preemption, and circular wait. Breaking any one of them prevents deadlock, and resource ordering targets the circular wait condition specifically.
Locks: Trading Concurrency for Correctness
So how do you actually solve these problems? Partly through design (adding a dedicated button to the coffee machine), partly through idempotency (using one-time tokens on web form submissions so the server ignores duplicate submissions), and most fundamentally through the engineering tool of locks.
The essence of a lock is Mutual Exclusion. Only the program that holds the lock can execute a particular section of code; all others must wait. Back to the milk example: the person who holds the lock has exclusive access to the entire flow of "check fridge → leave a note → go shopping." Others either find milk already there or see the note and know someone is already handling it.

In the banking scenario, you lock the specific account before withdrawing (you can't lock the entire bank — that would halt all transactions), then release the lock when done. Your spouse's ATM can't acquire the lock, so the program is suspended and waits. When it's finally its turn, it finds the balance is insufficient.
A great follow-up question: what if two machines try to grab the lock simultaneously? The answer lies in atomic operation primitives at the CPU level — hardware guarantees that exactly one party gets the lock and the other doesn't. This mechanism must be built into the CPU and memory system. The party that fails to get the lock can either spin in a polling loop, or "sleep" in the operating system for a short interval, yielding the CPU to other processes (like letting a video editor run for 16 milliseconds before retrying).
The cost of locks is reduced concurrency — they are a bottleneck. For data that is read frequently but written rarely, there are more efficient techniques (Computerphile teased a follow-up episode on this).
CPU-level atomic operation primitives are typically implemented as Compare-And-Swap (CAS) or Test-And-Set instructions. Take Test-And-Set: in a single, uninterruptible CPU cycle, it simultaneously reads the current value of a flag in memory and writes 1 to it. No matter how many threads execute this instruction at the same time, the hardware bus serializes the requests, ensuring only one thread reads 0 ("lock free") and successfully acquires the lock, while all others read 1 ("lock held") and fail. This is why lock correctness cannot be guaranteed by software alone — if "check lock status" and "claim the lock" were two separate instructions, a race condition would reappear in the gap between them. Higher-level synchronization primitives in modern operating systems — Mutexes, Semaphores — are ultimately built on top of these CPU-level atomic instructions.
This Isn't Just Theory: Race Conditions Are Everywhere
Race conditions are no laboratory curiosity. The opening analogy of two people talking over each other maps directly to real network protocols: multiple computers sharing an early Ethernet cable would detect a collision when the line was already occupied and implement a "collision → back off → retry" mechanism. Wi-Fi works the same way — all devices compete for time slots on the same frequency, and sometimes you don't even notice that someone else is transmitting too.
More insidiously, these bugs are largely emergent: they only surface when massive numbers of operations run concurrently in production environments. The gap between two instructions on a modern CPU is roughly one-third of a nanosecond — the window for something to go wrong is nearly invisible, but it does exist. This is precisely why concurrent programming is so hard to get right. Right now, as you read this, every device in your home is competing for bytes on Wi-Fi, racing to claim the channel to the internet.
Related articles

GitHub Daily · October 23: Agent Runtimes and Skill Libraries Surge on the Same Day
NVIDIA open-sources OpenShell, a Rust security runtime for autonomous agents; Claude-Red turns security methodology into SKILL.md skills; VoxCPM2 drops the tokenizer.

Build an AI Super Employee System at Zero Cost: A Full Breakdown of Automated Agent Workflows
A deep breakdown of the AI super employee system demoed on Bilibili: agent orchestration, digital human voiceovers, map and enterprise lead generation, 24/7 customer service, and legal modules. We analyze the business logic and compliance risks behind zero-cost signup to help entrepreneurs judge the real ROI of this AI digital employee system.

The Essential Difference Between Chatbots and AI Agents: Crossing from Conversation to Execution
What's the difference between a chatbot and an AI agent? Based on the MCP series on the Learn Tech channel, this article uses examples like booking flights and summarizing notes to explain: a chatbot only gives answers, while an agent actually completes tasks through a think-act-observe loop. It breaks down the four core components—model, tools, memory, and goal—as well as MCP's role as a bridge, and discusses agents' applicable boundaries and permission risks.