From Scarcity to Abundance: How Go Changed Concurrency Forever
We program like threads are expensive because, for decades, they were.A typical operating system thread weighs about 8MB. Start a thousand of them and you’ve consumed 8GB of memory before writing a single line of business logic. Most languages treat threads like precious resources — carefully pooled, cautiously managed, sparingly allocated.
But Go doesn’t care about any of that.
The old rules don’t apply
In 2009, when Go first appeared, spawning ten thousand concurrent tasks wasn’t just impractical — it was absurd. You’d carefully architect around thread limits, build complex pooling systems, and spend sleepless nights optimising connection handlers. Thread creation was expensive, context switching was costly, and stack space was sacred.
Go’s designers looked at this landscape and said: What if none of that mattered?
A goroutine starts with a 2KB stack. Not 2MB — 2KB. It can grow and shrink as needed. The Go runtime multiplexes thousands of goroutines onto a handful of OS threads using a scheduler that would make operating system designers weep with envy.
This isn’t just a performance optimisation. It’s a complete philosophical shift.
When threads become free, everything changes
I remember the first time I wrote go handleRequest(req)
without a second thought. No pool. No limit. No careful resource management. Just: here's a request, handle it concurrently. The simplicity felt wrong, like I was forgetting something important.
I was forgetting twenty years of threading orthodoxy.
Most concurrent programs are built around scarcity. We batch operations, queue requests, and carefully ration our precious threads. We build elaborate mechanisms to avoid the thing we actually want to do: handle each task as it arrives, independently, without coordination.
Go flips this on its head. Abundance changes the game entirely.
When you can spawn ten thousand goroutines without breaking a sweat, you stop thinking about resource management and start thinking about problem structure. Instead of “How do I efficiently share these expensive threads?” you ask “What’s the natural concurrent structure of this problem?”
The weight of lightness
A goroutine weighs almost nothing, but that lightness carries tremendous conceptual weight.
Consider a typical web server handling file uploads. In the old world, you’d carefully manage a thread pool, queue incoming requests, and hope you sized everything correctly. The code becomes a complex dance of resource management, timeout handling, and back-pressure.
// This feels reckless. It isn't.
func handleUpload(w http.ResponseWriter, r *http.Request) {
go processFile(r.Body)
go updateDatabase(fileInfo)
go sendNotification(user)
w.WriteHeader(http.StatusAccepted)
}
Three goroutines, spawned without ceremony. No pool, no limit, no apology. Each concurrent task maps directly to a natural piece of the problem. The code reads like the problem description.
This isn’t just about performance — though Go programs often are fast. It’s about cognitive load. When concurrency is free, you can structure your programs the way you think about the problems they solve.
The freedom to be wasteful
There’s something liberating about wastefulness when the thing you’re wasting is infinite.
I’ve seen Go programs that spawn a goroutine for every line in a log file, launch hundreds of goroutines to process a batch job, or create a dedicated goroutine just to handle cleanup for a single request. In any other language, this would be madness.
In Go, it’s Tuesday.
This abundance creates a different kind of discipline. You stop optimizing for thread efficiency and start optimising for clarity. You stop building complex machinery to manage resources and start building simple programs that do what they say.
The constraint of expensive threads forces you to build complex solutions. The freedom of cheap goroutines allows you to build simple ones.
What we’ve gained
When I look at Go codebases, I see something different from other concurrent programs. There’s less ceremony, fewer abstractions, less clever coordination. Problems are solved more directly.
A chat server doesn’t need a connection pool — each connection gets its own goroutine. A web scraper doesn’t need to carefully manage worker threads — each URL gets its own goroutine. A data pipeline doesn’t need complex batching logic — each record gets its own goroutine.
This isn’t always the most efficient approach. But efficiency isn’t the only virtue. Sometimes clarity matters more than optimization. Sometimes the right abstraction is no abstraction at all.
The ten thousandth goroutine
The number ten thousand isn’t magic. Many Go programs spawn far more goroutines than that. Some spawn fewer. The point isn’t the specific number — it’s the mindset.
When someone asks “But what if you need to handle ten thousand concurrent requests?” the Go programmer shrugs. That’s not a problem to solve. That’s just Tuesday.
This shift from scarcity to abundance changes how we think, how we code, and what we build. We stop building clever resource managers and start building clear, direct solutions. We stop fighting the concurrency and start embracing it.
The ten thousandth goroutine weighs the same as the first: almost nothing.
But together, they’ve changed everything.