53. Goroutine stacks and the scheduler, revisited
53.1 Growable stacks
A goroutine starts with a small stack (historically 2KB) and grows by copying to a larger allocation when it would overflow — checked cheaply at function-call prologues. This is why launching a goroutine per connection (Chapters 26, 38) scales to tens of thousands of concurrent connections without the multi-megabyte-per-thread cost a traditional one-thread-per-connection server pays.
53.2 What actually blocks a goroutine (and what doesn't)
Channel operations, mutex waits, and blocking syscalls park a goroutine without consuming an OS thread while parked (Chapter 28.2's syscall detachment). CPU-bound work with no goroutine-yielding operation used to be a scheduling risk before cooperative preemption existed everywhere (Chapter 28.4); today it mostly is not, though a truly pathological tight loop with function inlining that removes preemption checkpoints remains a rare edge case worth knowing about, not a daily concern.
53.3 Real-world example
The reason "spawn 50,000 goroutines to handle 50,000 concurrent WebSocket connections" is a completely normal, load-tested Go architecture — and the equivalent in a thread-per-connection language would need a very large machine or an event-loop rewrite — comes directly from this stack-growth-plus-scheduler design, not from any particular library.