Why were today's methods born in exactly this order? Each one came not from a bright idea but from the bill the previous generation was handed.
Thread pools, event loops and coroutines did not appear because someone had a good idea one morning. Each one exists because of the price the previous generation paid. Processes were heavy, so threads appeared. Threads could not survive the memory bill, so event loops appeared. Event-loop code became unreadable, so coroutines appeared. Step through the timeline below and follow what each era gained and what it paid.
Reading this history as "things kept getting better" teaches you nothing. Every step bought something by paying for it elsewhere. Time-sharing bought CPU utilization and paid in context switches. Threads bought lightness and paid in race conditions and locks. Event loops bought memory and paid in callback hell and a hard no-blocking rule. A newer model does not make an older one wrong — it changes what you pay.
Sixty years compress into three decisions. (1) Isolate — cut a running program into a unit whose context can be saved and restored, and forbid it from touching anyone else's memory (the process). (2) Share — drop the isolation and put many flows inside one address space (the thread). (3) Register — stop attaching a flow to a wait at all; just register it with the kernel's notification list (the event loop). Everything after that — coroutines, goroutines, virtual threads — is a question of how the third decision is packaged.
Here is the striking part: the term "coroutine" was coined in 1963, and Simula 67 shipped them as a language feature. Then they sat at the margins for forty years, because nothing needed that many concurrent things. Only when C10K forced the event loop, and the event loop produced callback hell, did the old idea become the right answer. Technology is not adopted because it is good; it is adopted when a problem demands it.
Code written with virtual threads in 2023 has the same shape as code written on Unix in 1969: straight down the page, blocking calls and all. What changed is underneath — the price of one wait fell from megabytes to kilobytes, and responsibility for the suspension point moved from the kernel to libraries, then to compilers, and now to the runtime. The circle closed, but not in the same place.
Step through the timeline and watch the diagram. A box is an address space (a process), a green bar is a kernel-scheduled thread, and a blue dot is a user-level task riding on it. In the 1950s there is one box with one bar; by 1969 the boxes multiply; by 1985 they collapse into one box while the bars multiply. In 1999 the bars stop multiplying, and from 2004 tens of thousands of blue dots pile onto a single bar. Read "what it cost" at every stop — that cost is exactly what called the next era into being.
Shape of the concurrent unit
program
▌flow of execution
What actually happened
The problem it inherited
This is the starting point. A computer cost as much as several houses and was rented by the hour. Yet that expensive CPU spent very little of its time actually computing.
What was invented
You submitted your program as a deck of punched cards, and an operator fed decks into the machine one at a time. Read cards, compute, print, move to the next deck. Exactly one program lived in memory.
What it cost
While the card reader or tape spun, the CPU genuinely did nothing — there was no other program in memory to switch to. Utilization in the single-digit percentages was normal.
What this era left behind →
"Could we give that expensive CPU to another program while this one waits?" That single question produced the next sixty years of inventions.
Related chapters
Try it in the simulator
Terms in this chapter
Why it evolved this way
The order was set by whichever resource was most expensive at the time, not by taste. In the 1950s and 60s the CPU dominated every other cost, so the answer was to never let it idle (time-sharing). By the 1990s CPUs were cheap but memory and thread stacks were the wall, so the answer was to cut the per-connection cost (event loops). By the 2010s hardware was cheaper than developer time and developer mistakes, so the answer was to keep the performance while making the code readable again (async/await, virtual threads). If you want to guess the next model, ask what is most expensive now.
Every execution model is one era's answer to a single question: who pays for the waiting?
The remaining problem
Finally, let us put all these methods in one table — actually executed, same conditions →