A line that queries a database — how much CPU does it use while it waits for the answer? A customer who has gone to the vault still occupies the counter, but no work is happening at it.
See this first in FoundationsWhy is one fetch instant and another slow?So far our code stayed inside the CPU. Real programs read files, receive network data, query databases — collectively, IO. The point of this chapter is simple: IO is astronomically slow by CPU standards, and while it happens your code is simply stopped.
An instruction takes about a nanosecond; a memory fetch about 100ns; an SSD read about 100µs; a network round trip milliseconds. Scale a CPU instruction to one second and the SSD read takes more than a day, the network reply a few months. That gap is the starting point of this whole series.
Call db.query() and it does not return until the result arrives, so the next line cannot run. That is blocking. In the animation the PC sits pinned to one line while the CPU indicator switches to "idle".
Remember chapter 4: held references cannot be reclaimed. A waiting function keeps its stack frame, and its variables keep pointing at heap data. So waiting code uses no CPU but does use memory — a combination that causes real incidents later.
This animation follows one program, so the CPU shows as idle. On a real machine it is not: the OS immediately puts other work on it. That is chapters 5 and 6.
One CPU instruction takes about a nanosecond. A memory access is around 100 ns, an SSD read tens of microseconds, a network round trip inside one datacenter about 0.5 ms, and across regions tens of milliseconds. In CPU terms, one disk access is tens of thousands of instructions and one remote call is tens of millions. That gap is where this entire series starts.
When waiting dominates, making the computation faster barely moves the total. Overlapping the waits does. Ten requests waited on one after another take ten times as long; waited on together they approach the cost of one. Thread pools, event loops and coroutines are all different answers to one question: how do we make that overlap cheap?
1handle(req) {2 id = parse(req)PC ◀3 user = db.query(id) // ask the DB and wait4 return toJson(user)5}
When a request arrives the server calls handle (chapter 1's [register + jump]). Pull the id out — CPU working (0.3ms). PC = 2.
Every duration is scaled up one billion times, so one CPU instruction feels like a second.
While you wait for one external call, the CPU could have run hundreds of millions of instructions.
Source: Jeff Dean · Peter Norvig, “Latency Numbers Every Programmer Should Know” (human-scale conversion: hellerbarde). Bars are on a log scale.
Try it in the simulator
Terms in this chapter
Why it evolved this way
CPUs run at semiconductor speed while disks and networks are bound by physics. The gap is permanent, so most execution models in computing history are different answers to "how do we make the waiting useful?"
The bottleneck is the wait. Waiting code uses no CPU but still holds memory.
The remaining problem
Could the CPU do something else meanwhile? First there has to be something else →