Why architecture matters here
Coroutines matter because they enable massive IO-bound concurrency cheaply -- many more concurrent operations than threads allow -- and they're the concurrency model of much modern async code (async/await, goroutines). IO-bound concurrency (many operations mostly waiting on IO -- network requests, connections) is common and important (servers handling many connections, clients making many requests). A thread per operation is expensive (OS threads have real memory and scheduling cost -- so thousands strain the system, and millions are impossible). Coroutines are far cheaper (a coroutine is a lightweight suspendable function -- far less overhead than an OS thread) -- so you can have millions of coroutines (millions of concurrent operations) on a few threads -- enabling massive IO-bound concurrency. And coroutines are the model of much modern async code (async/await in Python, JavaScript, Rust, C++, Kotlin; goroutines in Go) -- so understanding them is understanding how modern concurrent code works. For IO-bound concurrency (the common case for servers and network clients), coroutines are a key model, and understanding them (how they suspend/resume cheaply, the cooperative model, the disciplines) is essential to modern concurrent programming.
The cooperative-suspension insight is the architectural core, and it's why coroutines are cheap and numerous. A thread blocks when it waits (e.g., on IO) -- the OS thread is stuck (holding its stack, consuming a scheduling slot) until the wait completes -- so a thread per waiting operation is expensive (each stuck thread costing memory and scheduling). Coroutines instead suspend cooperatively: at a suspension point (awaiting IO), the coroutine yields control (voluntarily -- cooperatively) -- suspending its execution (saving its state compactly -- not holding an OS thread) and letting another coroutine run on the thread. When the awaited IO completes, the coroutine is resumed (its state restored, continuing from the suspension point). So a coroutine waiting on IO doesn't hold a thread (it's suspended -- its state saved, the thread free for other coroutines) -- unlike a blocked thread (which holds the thread). This means many coroutines share few threads (each coroutine holding a thread only while actively running -- not while waiting) -- so you can have vastly more coroutines than threads (millions of coroutines on a few threads -- each suspended while waiting, running only when ready). This cooperative suspension (yielding at suspension points -- not holding a thread while waiting) is why coroutines are cheap (no thread per waiting operation) and numerous (millions on few threads), and understanding it is understanding the core of coroutine concurrency.
And the never-block discipline is the load-bearing rule, because the cooperative model breaks if a coroutine blocks. Coroutines are cooperative -- they yield control at suspension points (await), letting other coroutines run on the thread. This works only if coroutines actually yield (suspend cooperatively) rather than block. If a coroutine makes a blocking call (a synchronous IO call, a blocking lock, a CPU-heavy computation without yielding), it blocks its thread -- and since many coroutines share that thread, blocking the thread starves all the other coroutines on it (they can't run -- the thread is blocked) -- defeating the concurrency (the event loop stalled, the other coroutines stuck). So the load-bearing discipline is never block in a coroutine: use non-blocking/async operations (that suspend cooperatively -- yielding the thread while waiting) instead of blocking ones, and offload unavoidable blocking work (blocking IO, CPU-heavy work) to a separate thread pool (not the coroutine event-loop threads -- so it doesn't block them). This is the same discipline as all cooperative/async systems (Node.js, async runtimes) -- never block the event loop. Violating it (a blocking call in a coroutine) is the classic, catastrophic mistake (starving the concurrency -- the whole event loop stalled by one blocking coroutine). Understanding the never-block discipline (coroutines must yield cooperatively, not block -- blocking starves the shared threads) is understanding the load-bearing rule of coroutine concurrency.
The architecture: every piece explained
Top row: the idea and scheduling. The idea: functions that can suspend (pause execution) and resume later -- without blocking a thread. Suspension points: explicit points where a coroutine can suspend -- await (awaiting an async operation) or yield (yielding a value/control) -- the coroutine suspending there and resuming later. Cooperative scheduling: coroutines voluntarily yield control at suspension points (cooperatively -- not preempted) -- letting other coroutines run on the thread. vs threads: coroutines are cheap and numerous (lightweight suspendable functions -- millions possible) vs threads (heavy -- OS memory/scheduling cost -- thousands strain the system) -- the concurrency-scale advantage.
Middle row: mechanics and structure. Event loop: drives the coroutines -- runs ready coroutines, and when one suspends (awaiting IO), runs another; resumes the first when its IO completes -- the scheduler of coroutines. Stackless vs stackful: two implementations -- stackless (the coroutine's state a compact object, suspension a state-machine transformation -- async/await) vs stackful (the coroutine has its own stack, suspended/resumed -- goroutines, fibers) -- different implementation approaches. Structured concurrency: scoped coroutine lifetimes (child coroutines bounded by a scope -- so they're awaited/cancelled together, not leaked) -- structuring the concurrency. Cancellation: cooperatively cancelling coroutines (signalling a coroutine to cancel -- it cooperatively checks and stops -- at suspension points) -- managing coroutine lifetimes.
Bottom rows: friction and comparison. Colored functions: the async/await 'coloring' -- async functions can only be called from async contexts (the async-ness propagating through the call graph -- 'coloring' functions async or not) -- a friction (the async coloring spreading through the code). vs virtual threads: coroutines (explicit async/await -- colored functions, explicit suspension) vs virtual threads (transparent suspension -- no coloring, blocking-style code that suspends transparently) -- the explicit vs transparent tradeoff (coroutines explicit about suspension; virtual threads transparent). The ops strip: blocking discipline (the load-bearing rule -- never block in a coroutine; use non-blocking operations, offload blocking work -- blocking starves the shared threads), cancellation (managing coroutine cancellation -- cooperative, at suspension points -- for cleanup and resource management), and backpressure (managing backpressure in coroutine-based streaming/pipelines -- so fast producers don't overwhelm slow consumers -- flow control in the async system).
End-to-end flow
Trace coroutines handling many concurrent requests. A server handles thousands of concurrent connections using coroutines (one coroutine per connection). Each connection's coroutine processes its requests -- and when it awaits IO (e.g., awaiting a database query or a network response), it suspends (yielding control cooperatively -- not blocking its thread) -- so the thread is free to run other connections' coroutines. The event loop drives this: it runs the ready coroutines (those not waiting), and when a coroutine suspends (awaits IO), the loop runs another ready coroutine; when a coroutine's awaited IO completes, the loop resumes it (continuing from the suspension point). So thousands of connection-coroutines share a few threads (each coroutine holding a thread only while actively running -- suspended while awaiting IO -- so the threads are multiplexed across the many coroutines). The server handles thousands of concurrent connections on a few threads (the coroutines suspending while waiting -- not a thread per connection) -- the cheap, numerous coroutine concurrency handling the IO-bound load.
The blocking-discipline and structured-concurrency vignettes show the disciplines. A blocking-discipline case: a coroutine makes a blocking call (a synchronous database call -- blocking, not async) -- blocking its thread. Since the thread is shared by many coroutines, blocking it starves them (the other coroutines on that thread can't run -- the event loop stalled) -- degrading the whole server (the concurrency defeated by the one blocking coroutine). The fix: use the async (non-blocking) database call (which suspends cooperatively -- yielding the thread while awaiting the query) instead of the blocking one -- or, for unavoidable blocking work, offload it to a separate thread pool (not the event-loop threads). The never-block discipline (async operations, offloading blocking work) kept the coroutines cooperative. A structured-concurrency case: a coroutine spawns child coroutines (e.g., to fetch several things concurrently) within a scope -- structured concurrency ensures the children are bounded by the scope (awaited together, and if the scope is cancelled or errors, the children are cancelled -- not leaked) -- so the child coroutines have well-defined lifetimes (bounded by the scope -- no leaked coroutines) -- the structured concurrency managing the coroutine lifetimes cleanly.
The cancellation and virtual-threads vignettes complete it. A cancellation case: a request is cancelled (the client disconnected, or a timeout) -- so its coroutine should be cancelled (stopping its work, freeing resources). Cooperative cancellation signals the coroutine to cancel -- and the coroutine cooperatively checks for cancellation (at its suspension points) and stops (cleaning up -- releasing resources) -- so the cancelled request's coroutine stops promptly (cooperatively -- at a suspension point) without leaking. A virtual-threads case: the team notes the colored-function friction (the async/await coloring spreading through their code -- async functions callable only from async contexts) -- and considers virtual threads (where suspension is transparent -- blocking-style code that suspends without coloring) as an alternative (avoiding the coloring, at the cost of the explicitness) -- weighing the explicit (coroutines) vs transparent (virtual threads) tradeoff. The consolidated discipline the team documents: use coroutines for cheap, massive IO-bound concurrency (suspending cooperatively at await points -- millions of coroutines on few threads -- far cheaper than threads), never block in a coroutine (the load-bearing discipline -- use async operations, offload blocking work -- blocking starves the shared threads), use structured concurrency (scoped coroutine lifetimes -- no leaks) and cooperative cancellation (for cleanup), manage backpressure (in streaming/pipelines), understand the implementation (stackless vs stackful) and the colored-function friction (vs virtual threads' transparency), and drive it with an event loop -- because coroutines enable massive IO-bound concurrency cheaply (cooperative suspension -- not a thread per operation), the model of much modern async code, with the never-block discipline as the load-bearing rule.