When you choose a programming language for a project, many relevant factors come in. Most technical people focus on these two: Does it have the features, packages, and performance characteristics I need for my problem space? Is it built to run in the environment I need it to work in (servers, desktop, mobile, embedded, etc.)? With respect to these questions, many languages are great choices.
I remember when our team first encountered Go around 2013. Our back-end cluster services were all in C++. Go was a baby language out of Google, and though I’m cautious about novel things, we had great respect for Google’s engineering culture.
We found that Go excels on a further set of criteria that some people ignore. I think these criteria are what makes it not merely a smart language, but a wise one.
Wisdom is the ability to make good decisions. Making wise decisions requires lots of context, vision, and judgment. (By this definition, AIs are brilliant, but foolish.)
This post is not a tour of all of Go’s features, nor an argument that it’s the right tool for every job. It’s a way of appreciating a few software engineering values demonstrated by the language, ones I think every software leader should value.
Long Life is Long Value
One is Go’s emphasis on stability. Code we wrote in 2014 (Go 1.3) still works perfectly today. The creators have essentially said, “there may never really be a Go 2”.
I expect younger engineers may be less surprised by this than veterans. But once you’ve spent some time in the industry and tried to resurrect a 10+ year-old Java or Python project (don’t even get me started on Javascript), you’ll realize how much value this stability has. Software engineering, unlike mere programming, is building systems that last over time. If you cut in half the effort required to do upgrades and security fixes, you’ve bought back time to add more value.
This emphasis is not just in the language design, but in package management and in the Gopher culture at large. If you push a backwards-incompatible change to a package without updating the major version, your users will cry foul, because you violated the module release and versioning workflow. Though it took a while to settle how dependency management works in Go, I can testify that how Go Modules work (especially requiring a new import path for major versions) has been a wonderful balance between stability and improvements.
Clearly there is a cost to this. Maybe you have that one function you’d like to alter, just a teeny bit, regretting your earlier design choice. The change for users would be so easy to make. Trying to leave it be feels like you’re bending over backwards. Is it really so bad to just do it, without the overhead of a major release? I think Gopher culture is wise to say: yes.
The Battle Against Complexity
A feature of Go that impressed me early on was how few features they aimed to put in the language.
I think of one common irritant to newcomers, that there’s no concise “try-catch” semantic. All the if err != nil { ... } blocks are verbose, and I’d like a nice solution as much as anyone. It’s been attempted many times. So why not add it? Or ternary operators? Or built-in, type-safe enums?
For one, caution about new additions is related to my previous point. The Go authors recognize that once a change is out, it can’t simply be taken back, because software using it needs to keep working.
But beyond that, it keeps the language simple. Here’s a laundry list of things unlocked by keeping it simple:
Easy onboarding
Consistency of style (and I can vouch here, even the standard library is easy to read)
Reduction in migrating to the “new way”
Extremely fast builds (and every other source-reading task, like linting)
Robust automated changes (e.g. gofix)
Amazing internal runtime improvements (e.g. the garbage collector)
I like to joke that “Fear of complexity is the beginning of wisdom” should be added as a Go Proverb. If the language were a bit more complex, each item in this list would be that much harder.
When I look at code that seems a little too verbose or not as “ideal” as I’d like, I can metabolize it by recognizing the many benefits that come to me because of simplicity.
Prepared for the Future
Go is often touted as future-ready because of built-in concurrency primitives like goroutines. I want to explain here what I found refreshing about goroutines versus other asynchronous programming patterns other languages have.
If I’m giving a computer a set of instructions, I don’t want it littered with distracting concurrency logic. If I want to write an endpoint that fetches a user’s session, does a database read, then sends a response with some data, in that order, I want to write exactly that. This is how humans think.
Of course, if I want to do two reads in parallel, then I need concurrency logic. It should be plain and simple to see. I shouldn’t need unnecessary event loop executions, callbacks, awaits or CompletableFutures (prior to Virtual Threads).
Go is prepared for the future that’s already here: high concurrency across many CPU cores, on whatever platform you need to run on, with a runtime free of cold starts.
Closing Thoughts
I hope this post can help someone with less experience appreciate Go a little bit more. This same desire to make wise choices drove me to create Katabole, a web framework (really more of a reference architecture), which I’ve been using for internal projects. The design goals in the FAQ there were inspired by Go itself.
Ask yourself:
When I choose languages or libraries, am I keeping the future in mind?
How might this extend to other technology choices I need to make, or that I’m obligated to use?

