Should Go Still Hold On to Its Philosophy?


Go is an extremely opinionated language.

It does not like “magic.” It wants you to spell things out: handle errors explicitly, keep dependencies visible in your code, and avoid hiding too much behind abstractions.

By “magic” I don’t mean it’s literally mysterious. I mean the kind of functionality where you can’t tell how something works just by reading the code — framework conventions, annotations, implicit dependency injection, or behaviors buried deep inside the runtime.

This philosophy was particularly visible in Go’s early days. For a long time, Go didn’t even have generics.

I remember around Go 1.6, we needed to write our own language. After parsing with yacc, we had to process tokens and syntax nodes by hand. Without generics or stronger abstraction tools, we relied on type assertions to guess types, layer after layer of error handling on top.

It was genuinely painful.

The restraint paid off

But I’m not saying this approach was bad. In fact, it brought a huge benefit: whether you’re a beginner or a veteran, the code you write is usually readable by others.

No excessive abstraction. No jumping through half a dozen interfaces just to trace one implementation.

Compare that to C++ or Java, where the gap between what a senior engineer writes and what a junior writes can be enormous. Reading a senior’s code is itself a skill. Go’s philosophy lowers that barrier. Projects become easier for newcomers to maintain.

Given enough time, the code is almost certainly understandable.

This approach works beautifully at small to medium scale. It shines in middleware development, high-concurrency queues, schedulers — that kind of thing.

That’s exactly why so many companies and teams migrated to Go around 2018.

But when business logic meets Go, the picture gets more complicated. There are still advantages, but the bottlenecks start to show.

Let’s start with the good parts.

Same as before: the code is easy to write and easy to read. You don’t need to hire “Go developers.” You hire experienced backend engineers, give them three months, and they’ll be writing production business logic with confidence.

This doesn’t just make projects maintainable — it’s great for the Go ecosystem as a whole. Compared to Python, which is similarly easy to write, Go has clear advantages in its type system, gofmt, dependency management, and cross-compilation.

Another plus is Go’s concurrency model.

Business code frequently needs concurrency. Doing that in Java or C++ used to be tedious. Go’s goroutines, paired with tools like errgroup and sync.WaitGroup, dramatically reduce the cognitive load. Concurrency just isn’t that complicated anymore.

But business code in Go can get heavy

Now for the downsides.

First, verbose code drastically reduces information density.

When you’re scanning through Go code quickly, the screen never feels big enough. Your brain automatically filters out the error-handling boilerplate — it becomes visual noise occupying a huge portion of the file.

Reading this kind of code sometimes feels like reading a logbook. Plenty of necessary, low-information content.

I’m not saying Go’s error handling is bad. Actually, I think explicit error handling is a very good thing. But objectively, a significant portion of Go’s verbosity comes directly from it.

Second, there’s Go’s philosophy of “simplicity” — or more accurately, “restraint.”

Take generics as an example. Before Go 1.18, you could approximate them with type discrimination, method duplication, or code generation. But all of those hurt maintainability.

Imagine a machine with 100 buttons. You achieve different outcomes through combinations of on/off states.

Many languages provide syntactic sugar that lets you wrap common combinations into templates, so the user doesn’t have to remember how to press every button. Go’s attitude is closer to:

No. We want you to fully understand what you’re doing.

So everyone has to press the buttons manually.

To be fair, the former approach can cause problems. Bad abstractions mislead users about what’s underneath, and they can strip away the caller’s ability to customize deeply.

But exposing every internal detail isn’t necessarily the answer either.

Human brains are finite. The people maintaining the code change over time. Relying entirely on individual judgment creates plenty of unnecessary incidents too.

This isn’t purely a language problem

To put it bluntly: Go’s designers don’t want to take on too much of the “tool’s” responsibility.

Exposing every internal detail can feel like hands-off design: here are your options, you figure it out.

But not every programmer is a highly skilled programmer. Not everyone can maintain a deep enough understanding of both the language and the business logic. Eventually, the codebase becomes repetitive and bloated. Even with experienced team members, they can’t forever run defense for everyone else.

What I’m describing isn’t a pure programming language problem, nor a scientific one. It’s an engineering problem. It’s also a management problem.

I get the appeal of no magic. I personally despise magic, because learning magic is often a twisted process — you’re reduced to experimenting your way toward guesses about how things actually work.

But magic has clear benefits too.

It lets business programmers lift their attention away from pure code syntax and focus more on business processes and logic itself. That might not matter much for middleware, but it matters enormously for building e-commerce, payments, CRM — those kinds of systems.

To let junior members deliver value on a team, we need them to spend less time wrestling with manual dependency injection, less time writing the same boilerplate inside for loops, and more time thinking about how data flows and how business rules should be expressed.

Go at the crossroads

So yes, Go has arrived at a crossroads.

It needs to decide what kind of language it wants to become.

If it wants to occupy the space Java and C# hold, it will have to accept more abstraction — maybe even a little magic.

If it wants to stay radically simple, it might need a lighter-weight, more script-like usage model, so small tools and quick tasks don’t always demand full project mode.

If it wants to become safer, then Rust is a direction worth studying.

For now, Go stands at this crossroads, philosophy in hand, still hesitating.