Go doesn’t get the kind of chip-launch coverage that Nvidia or AWS silicon gets, but it quietly underpins a huge share of the infrastructure those chips run on. Kubernetes, Docker, Terraform, Prometheus, and etcd are all written in Go, and that’s not a coincidence — the language was designed from the start around two things cloud infrastructure needs constantly: handling many things happening at once, and staying simple enough for large teams to maintain. Here’s what’s actually in the language, including the 2022 addition of generics, and why so much of the infrastructure layer keeps choosing it.
At a Glance
| Feature | What It Does | Added |
|---|---|---|
| Goroutines | Lightweight concurrent functions multiplexed onto OS threads | Go 1.0 (2012) |
| Channels | Typed pipes for goroutines to communicate and synchronize | Go 1.0 (2012) |
| Generics (type parameters) | Functions/types that work across multiple types without losing type safety | Go 1.18 (March 2022) |
| Built-in fuzzing | Automated test-input generation via go test -fuzz | Go 1.18 (March 2022) |
Goroutines: Concurrency Without the Thread Management
A goroutine is, in Go’s own documentation, “a function executing concurrently with other goroutines in the same address space.” The Go team deliberately avoided calling them threads, because goroutines are much cheaper: each one starts with a small stack that grows and shrinks as needed, and the Go runtime multiplexes many goroutines onto a much smaller number of OS threads. Starting one is as simple as putting the keyword go in front of a function call — the goroutine runs independently, and the caller doesn’t wait for it to finish. That cost difference is a big part of why Go code can comfortably run tens of thousands of concurrent operations — incoming requests, open connections, background jobs — that would be expensive to run as that many OS threads in other languages.
Channels: How Goroutines Actually Talk to Each Other
Goroutines aren’t very useful on their own if they can’t coordinate, and that’s what channels are for. A channel, created with make(chan T), is a typed conduit that one goroutine can send values into and another can receive values from. An unbuffered channel blocks the sender until a receiver is ready, which gives you communication and synchronization in a single operation; a buffered channel (make(chan T, N)) lets a sender proceed until the buffer fills. Go’s own documentation sums up the resulting style this way: “Do not communicate by sharing memory; instead, share memory by communicating.” Rather than multiple goroutines fighting over a shared variable with locks, they pass values through channels instead.
A simple worker-pool pattern, a staple of Go network and infrastructure code, looks like this:
package main
import "fmt"
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
results <- j * 2
}
}
func main() {
jobs := make(chan int, 5)
results := make(chan int, 5)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
for a := 1; a <= 5; a++ {
fmt.Println(<-results)
}
}
Here, three worker goroutines all read from the same jobs channel; whichever one is free next picks up the next job, and each result is sent back on a separate channel for the main goroutine to collect. Patterns like this — a fixed pool of goroutines draining a shared channel — show up constantly in Go services that need to process many requests concurrently without spawning an unbounded number of goroutines.
Generics: Go 1.18’s Biggest Change
For its first decade, Go had no way to write a function or type that worked across multiple types without either duplicating code or falling back to interface{} and losing type safety. Go 1.18, released in March 2022, fixed that by adding type parameters. A function or type declaration can now take type parameters in square brackets, and interfaces can define “type constraints” — sets of types a type parameter is allowed to be, rather than just sets of methods. Go 1.18 also introduced two new predeclared identifiers: any, an alias for interface{}, and comparable, the set of types that support == and !=. The change was designed to be fully backward-compatible — existing Go code didn’t need to change — though the Go team cautioned at the time that generic code “has not had much production testing” and recommended appropriate caution deploying it. The same release shipped Go’s built-in fuzz testing support (go test -fuzz), a register-based calling convention extended to more CPU architectures for a roughly 10% performance gain on some platforms, and a revised append growth strategy, among other runtime changes.
Why Cloud Infrastructure Keeps Choosing Go
None of this explains, by itself, why Go became the default language for so much cloud-native tooling — Kubernetes, Docker, Terraform, Prometheus, and etcd are all implemented in Go. But the companies that have published their own reasons for adopting it, via Go’s official case studies, point to a consistent pattern. Dropbox moved performance-critical backends off Python for, in its words, “better concurrency support and faster execution speed.” Monzo called Go “a perfect language for creating microservice architectures.” Stream said Go’s “fast performance, solid support for concurrency” helped it power real-time feeds and chat for more than 500 million users. CockroachDB’s team cited “performance benefits, garbage collection, and low barrier to entry.” Netflix picked Go in part because it offers lower latency than Java while being more approachable than C, and because it can comfortably handle tens of thousands of simultaneous connections — precisely the shape of problem infrastructure tools like a container orchestrator or a reverse proxy have to solve. Compiled, statically-linked Go binaries also deploy as a single file with no separate runtime to install, which matters a great deal for software that has to run identically across thousands of servers or inside minimal containers.
Trade-offs
- Goroutines are cheap, but cheap doesn’t mean free — spawning millions of them without a limiting pattern like a worker pool or semaphore can still exhaust memory.
- Generics closed a real gap, but Go’s own 1.18 release notes flagged real limitations (no type declarations inside generic functions, no field access on type-parameter values) that shaped how cautiously teams adopted them early on.
- Go’s simplicity is also a constraint: the language and standard library deliberately omit features, like exceptions or certain forms of operator overloading, that some teams coming from other languages miss.
- Being the language cloud-native tooling is written in doesn’t mean every team building on Kubernetes or Terraform needs to write Go themselves — most only interact with those tools’ APIs or CLIs.
Bottom Line
Go’s core pitch hasn’t changed much since 2012 — goroutines and channels make concurrency manageable, and the language stays deliberately small — but generics in 2022 closed one of its longest-standing gaps without breaking that simplicity. Combined with fast compilation and single-binary deployment, that combination is why so much of the software managing cloud infrastructure itself, rather than running on top of it, continues to be written in Go.
FAQ
What’s the difference between a goroutine and a thread?
A goroutine is managed entirely by the Go runtime, not the operating system. It starts with a small, growable stack, and many goroutines are multiplexed onto a much smaller number of OS threads, which makes goroutines far cheaper to create than OS threads.
Do I need a channel every time I use a goroutine?
No, but without some way to communicate or synchronize — a channel, a sync.WaitGroup, or similar — there’s no way to know when a goroutine has finished or to safely exchange data with it.
When were generics added to Go?
Go 1.18, released in March 2022, added type parameters (generics) to the language, along with the any and comparable predeclared identifiers.
Why are so many cloud infrastructure tools written in Go?
Companies that have published their reasoning cite Go’s concurrency model, fast execution, and simple deployment as a single static binary as recurring reasons, according to Go’s own published case studies.
Sources
Discover more from Software Online Review
Subscribe to get the latest posts sent to your email.
