1.1 Why Go Was Created: Frustration at Google and the Three Authors
Let’s be honest: you don’t create a new programming language because everything is sunshine and rainbows. You do it because you’re frustrated. Profoundly, pull-your-hair-out frustrated. That was the state of Robert Griesemer, Rob Pike, and Ken Thompson at Google around 2007. These aren’t exactly lightweights; we’re talking about the co-creator of Unix (Ken), a co-creator of UTF-8 (Rob), and a key contributor to the Java HotSpot VM (Robert). They’d seen things.
Their frustration wasn’t with some niche academic problem. It was with the industrial-scale misery of building server software at Google. They were watching C++ compiles that took 45 minutes for a single binary. They were wrestling with the baroque complexity of languages that had grown overstuffed with features. They saw a million-line codebase where no one could understand the whole thing, where dependency trees were nightmares, and where a single obscure feature could introduce a subtle, catastrophic bug. They wanted to burn it all down and start over. So they did.
The Pre-Go World at Google
Imagine you’re a developer at Google in the mid-2000s. Your toolchain is a beast. You’re mainly using C++ for its performance and Java for its productivity (or at least its garbage collection). But both are starting to crack under the weight of Google’s scale.
C++ is a powerhouse, but it’s also a minefield. Its compile times are legendary in the worst way. Its template error messages are a form of psychological torture. Its “undefined behavior” is a monster lurking under the bed, waiting to bite you. You spend more time managing memory and fighting the compiler than you do thinking about your actual problem.
Java is better, but it’s heavy. It feels like you need an entire application server just to say “Hello, World.” The JVM startup time, while better than a C++ compile, is still not trivial. And the language itself, while more sane than C++, is… verbose. Extremely verbose. It feels like you’re writing the same boilerplate over and over again, ceremony for the sake of ceremony.
What was missing was a language that felt like a dynamic language (Python, JavaScript) to write but performed like a compiled language (C++, Java). Something you could read quickly, write quickly, and compile quickly. Enter Go.
The Core Design Goals: A Manifesto of Pragmatism
The trio didn’t set out to create a language with revolutionary new concepts. Their goal was ruthlessly pragmatic: to combine the best parts of existing languages into a new one that was specifically tailored for the problems they faced every day. The design goals weren’t academic; they were a direct response to pain points.
Simplicity and Readability: This is Go’s north star. The language is deliberately small. It has just 25 keywords. You can hold the entire spec in your head. Why? Because if the language is simple, code is predictable. You can look at any piece of Go code, even code you’ve never seen before, and understand what it does. There are no hidden behaviors or complex DSLs magicked into existence by operator overloading. What you see is what you get. This is a direct rebellion against C++’s “smart” features that made code unreadable.
Fast Compilation: This was non-negotiable. The 45-minute C++ builds were a massive drain on productivity. Go was designed from the ground up for fast compilation. How? By having a clean syntax that’s easy to parse, by ditching a complex type hierarchy (no inheritance!), and by making dependency management trivial and explicit. Your
go buildshould take seconds, not minutes. This speed completely changes your development workflow; it makes the compiler a friend you consult constantly, not an ogre you avoid.Concurrency as a First-Class Citizen: Writing correct, multi-threaded code in C++ or Java is hard. It’s a great way to introduce race conditions and deadlocks. Go attacked this head-on with goroutines and channels. Goroutines are fantastically cheap lightweight threads managed by the Go runtime, not the OS. You can spin up a million of them without breaking a sweat. Channels are the built-in, typed conduits that let them communicate safely. It’s not that Go invented these ideas (they come from languages like CSP), but it baked them into the language so elegantly that they become the obvious way to do things.
// A classic example: spinning off work into a goroutine. package main import ( "fmt" "time" ) func slowFunction(resultChan chan<- string) { time.Sleep(2 * time.Second) // Simulate slow work resultChan <- "All done!" // Send the result back on the channel } func main() { myChannel := make(chan string) go slowFunction(myChannel) // Non-blocking call. The work happens elsewhere. fmt.Println("I'm doing other stuff right away...") result := <-myChannel // This *will* block until slowFunction sends its result. fmt.Println(result) }The beauty here is the clarity. The channel type
chan<- stringclearly indicates it’s a send-only channel for strings. The communication is explicit and safe.Garbage Collection: Manual memory management (malloc/free) is a huge source of bugs. The designers wanted it gone. But they needed a GC that was good enough not to get in the way. Early versions of Go’s GC had noticeable pauses, but this was deemed an acceptable trade-off for safety and simplicity. The team has since relentlessly optimized it into one of the world’s best low-latency GCs, but the initial goal was just “have one that works.”
Strict and Simple Dependency Management: Look at any Go file. The dependencies are right at the top. The compiler doesn’t have to go on a magical mystery tour to figure out what you’re using. This explicit importing prevents dependency bloat and, again, aids readability. You know exactly what each file needs.
The Questionable Choices (Let’s Be Real)
Go’s stubborn pragmatism leads to some… interesting omissions that feel like architectural oversights to outsiders.
No Generics (Initially): This was the big one. For a decade, the lack of generics was Go’s giant, glaring wart. It led to an epidemic of code generation, empty interface (
interface{}) abuse, and copy-pasted functions. The designers argued that simplicity was more important, and they weren’t wrong that generics complicate a language. But the community’s need for type-safe containers and algorithms eventually won. Generics were finally introduced in Go 1.18, and while they’re a welcome addition, their fairly complex syntax is a testament to the difficulty of bolting such a feature onto a language that wasn’t initially designed for it.Error Handling Instead of Exceptions: Go uses explicit error return values. You will be writing
if err != niluntil the end of time. It’s verbose, and it feels tedious. Why? Because the designers believed that exceptions break the flow of control and make it too easy to ignore errors. By making you handle each error right where it happens, your program’s error paths become first-class citizens, not an afterthought. It’s a trade-off: you gain explicit control and readability at the cost of verbosity. You might hate it, but you’ll never unknowingly swallow an error.package main import ( "fmt" "os" ) func main() { file, err := os.Open("myfile.txt") if err != nil { // You MUST deal with this. Right here. Right now. fmt.Println("Error opening file:", err) return } defer file.Close() // Another brilliant pragmatism: 'defer' for cleanup. // ... use the file }
So, why was Go created? It was created as a tool to get work done. It’s a language built by engineers, for engineers, born from the very real, very messy problems of building software at a scale most of us can barely imagine. It prioritizes the reader over the writer, the team over the individual, and simplicity over cleverness. It’s not a perfect language, but it’s a profoundly practical one. And that, it turns out, is exactly what a lot of us needed.