Golang / GoLang Concurrency Mastery Interview Questions
Why can Go run millions of goroutines while equivalent OS-thread workloads fail?
Three fundamental differences make goroutines dramatically cheaper than OS threads: initial stack size, scheduling cost, and blocking behaviour.
| Aspect | OS Thread | Goroutine |
|---|---|---|
| Initial stack | 1–8 MB (fixed, kernel-allocated) | 2 KB (grows dynamically up to 1 GB) |
| Scheduling | Preemptive by OS (expensive context switch with full register save) | Cooperative + async-preemptive by Go runtime (user-space, cheap) |
| Blocking | Blocks the entire OS thread on syscall | Parks goroutine; OS thread freed for other goroutines |
| Creation cost | ~10 µs, requires kernel call | ~300 ns, entirely user-space |
| Practical limit | ~10,000 before memory exhaustion | ~1,000,000+ on standard hardware |
package main import ( "fmt" "runtime" "sync" ) func main() { const n = 1_000_000 var wg sync.WaitGroup wg.Add(n) for i := 0; i < n; i++ { go func() { defer wg.Done() // Each goroutine: ~2 KB stack at creation // 1M goroutines â 2 GB total â feasible on modern hardware }() } wg.Wait() fmt.Println("done, goroutines:", runtime.NumGoroutine()) }
The small initial stack is possible because Go uses copy-on-grow stacks: when a goroutine needs more stack space the runtime allocates a new, larger stack, copies the old contents, updates all internal pointers, and frees the old stack. This is transparent to user code.
More Related questions...