
What Go Got Right and Wrong: A Thoughtful Look Back
A reflective and opinionated look at the Go programming language, combining insights from co-creator Rob Pike with real-world developer experience. This article explores Go’s strengths, trade-offs, and where it truly shines or falls short in modern software development.
What Go Got Right and Wrong: A Thoughtful Look Back
Go, often called Golang, has been with us for well over a decade. Designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson, it grew from an internal productivity experiment into one of the most influential backend and cloud-native languages in the industry.
This post combines insights from Rob Pike’s retrospective talk, as covered by The New Stack, with broader community experience and my own perspective as a developer who has seen Go work extremely well in some contexts and feel limiting in others.
Original article:
https://thenewstack.io/golang-co-creator-rob-pike-what-go-got-right-and-wrong/
Why Go Exists in the First Place
Go was created to solve very practical problems:
- Slow compile times in large C and C++ codebases
- Complex and fragile build systems
- Concurrency being powerful but difficult to write correctly
- Inconsistent tooling and formatting across teams
From the beginning, Go optimized for developer productivity over language cleverness. That single decision explains many of its strengths and many of its shortcomings.
What Go Got Right
Simplicity and Readability
Go’s syntax is intentionally small. There is usually one obvious way to do something, and the language avoids features that tend to fragment style or readability.
func greet(name string) string {
return "Hello, " + name
}
This kind of clarity scales well in large teams. In practice, Go codebases are often easier to onboard into than many alternatives because there are fewer stylistic and architectural surprises.
Fast Compilation and Feedback Loops
One of Go’s most underrated strengths is how fast it compiles. This directly shapes how developers work. Frequent builds, frequent tests, and fast iteration feel natural.
That tight feedback loop matters more than many language features people debate about.
Practical Concurrency
Go’s goroutines and channels make concurrent programming accessible without exposing developers directly to threads and complex locking patterns.
go func() {
results <- doWork()
}()
This model is not perfect, but it dramatically lowered the barrier to writing concurrent network services. This is a major reason Go dominates in cloud infrastructure and backend services.
First-Class Tooling
Tools like gofmt, go test, and go mod are part of the language experience, not optional add-ons. This removes entire categories of debate and friction.
From a professional standpoint, this is one of Go’s biggest wins. Teams spend less time arguing about formatting and more time delivering software.
Simple Deployment
Producing a single static binary with no runtime dependencies is a huge operational advantage.
go build
./myservice
For containers, command-line tools, and infrastructure software, this alone can justify choosing Go.
What Go Got Wrong or Accepted as Trade-Offs
Verbose Error Handling
Go’s explicit error handling is honest and predictable, but it is also repetitive.
file, err := os.Open("data.txt")
if err != nil {
return err
}
In small examples this is fine. In larger systems, it adds noise and can distract from core logic. This remains one of the most common pain points among experienced Go developers.
A Minimal Type System
Go intentionally avoids advanced type system features such as sum types, pattern matching, and expressive generics. Even after generics were added, they remain conservative by design.
This keeps the language simple, but it also makes complex business domains feel more verbose and less safe than they could be.
Concurrency Without Strong Guarantees
Goroutines are easy to start and easy to leak. Go does not provide structured concurrency or compile-time guarantees against common concurrency mistakes.
go doSomething()
// Who owns this goroutine and when does it stop?
This flexibility is powerful, but it also places a lot of responsibility on developers. Compared to newer languages, this is one area where Go shows its age.
Garbage Collection and Predictability
Go’s garbage collector has improved significantly over the years, but it still introduces nondeterminism. For most backend services this is acceptable, but for low-latency or real-time systems it can be a deal breaker.
Ecosystem Gaps Outside Backend Work
Go excels at servers, APIs, CLIs, and infrastructure tooling. It is far less compelling for GUIs, game development, or data science. These gaps are not accidental, but they do limit where Go feels like the right tool.
My Overall Take
I largely agree with Rob Pike’s reflections. Go succeeded because it optimized for real-world productivity, not theoretical elegance. Many of its rough edges are the direct result of deliberate trade-offs.
If I were designing a language today, I would likely make different choices around type expressiveness and concurrency guarantees. Even so, Go remains one of the best tools available for building reliable, maintainable backend systems at scale.
Go does not try to be everything, and that restraint is both its greatest strength and its most frequent criticism.
When I Recommend Go
- Backend services and APIs
- Cloud-native and infrastructure tooling
- Command-line applications
- Systems where build speed and deployment simplicity matter
When I Look Elsewhere
- Domains with complex business rules and rich domain modeling
- GUI or frontend-heavy applications
- Systems requiring strict real-time guarantees
Final Thoughts
Go is a language that rewards pragmatism. It asks developers to accept some verbosity and limitations in exchange for clarity, stability, and excellent tooling. Whether that trade-off is worth it depends less on personal taste and more on the problem being solved.
Understanding why Go made the choices it did makes it much easier to decide when it belongs in your next project.