I spend a lot of time reviewing code these days. Some of it is written by humans I know and trust. More and more of it is written by Claude, Cursor, or Copilot—and then merged by humans who are in a hurry.

There’s a dirty secret about that AI-generated Go: it’s old.

Not “legacy system from 2015” old, but “pre-generics, pre-maps package, pre-range-over-int” old. Large language models are trained on the massive corpus of Go code that exists on the internet, and the internet’s Go code skews toward what people wrote three, four, or five years ago. The result? Your shiny new feature might be built with interface{} instead of any, manual for loops instead of maps.Keys, or helper functions instead of the built-in min and max that landed in Go 1.21.

I was floored when the Go team explicitly called this out in the Go 1.26 blog post. They said that in the wake of LLM adoption, they noticed these tools “tended—unsurprisingly—to produce Go code in style similar to the mass of Go code used during training, even when there were newer, better ways to express the same idea.”

That’s not a dig at AI. It’s an observation about how code ages in an ecosystem that values stability.

Enter the Modernizers

Go 1.26, released in February, completely rewrote go fix. It’s no longer just a tool for API migrations after breaking changes. It’s now a suite of “modernizers”—static analyzers that find outdated patterns and rewrite them to use newer, cleaner idioms.

You can preview what it finds without touching your files:


go fix -diff ./...

Or let it run across your entire module:


go fix ./...

I ran it on a few of our active repositories last week. The results were revealing.

What It Found in Our Code

The modernizers caught dozens of small sins. None of them were bugs. All of them were friction.

interface{} everywhere. AI assistants love interface{}. It was the idiomatic way to express “any type” for a decade, so the training data is full of it. Go 1.18 introduced any as a type alias. go fix swapped them out automatically.

Manual map iteration. We had loops gathering map keys into slices—exactly the kind of boilerplate that maps.Keys from the maps package eliminates. The modernizer replaced a five-line loop with a single, clear function call.

Pre-1.22 loop variable shadowing. Remember x := x inside loops to avoid closure bugs? Go 1.22 fixed the loop variable semantics, but a lot of AI-generated code still carries the old workaround. go fix deletes the noise.

String concatenation in loops. Old habits die hard. AI tools still generate s += thing inside loops instead of strings.Builder. The stringsbuilder modernizer caught a few of these that had snuck back in.

Helper functions for min and max. Go 1.21 added built-in min and max. We had a surprising number of clamp() and minInt() helpers—some written by humans, some suggested by Copilot—that go fix collapsed into single expressions.

None of these changes altered behavior. That’s the point. The Go team designed the modernizers to be safe: compiler-verified, semantics-preserving, and aware of your go.mod version. If your module declares go 1.21, it won’t suggest features from 1.26.

Why This Matters More Than It Seems

In an industry obsessed with the next shiny framework, I bet on stability. Go’s compatibility promise is why I keep choosing it for the systems we build at Symbol and Wawandco. But stability doesn’t mean stagnation. The language has genuinely improved since 1.18. Generics, the maps and slices packages, min/max, integer range loops—these aren’t gimmicks. They reduce the amount of code I have to read, review, and maintain.

The problem is that knowledge of these improvements doesn’t spread instantly. It takes years for new idioms to displace old ones in Stack Overflow answers, blog posts, and GitHub repos. And since LLMs learn from that same corpus, they lag behind the language by design.

go fix closes that gap. It’s a tooling layer that compensates for the slow diffusion of idioms. You don’t have to manually audit every file to see if a junior dev (or Claude) used an outdated pattern. The toolchain does it for you.

Making It Stick

I’ve started treating go fix like gofmt: something you run, not something you debate.

Our CI now has a step that runs go fix -diff ./... and fails if there are unapplied modernizations. It’s non-blocking for now—we’re not dogmatic about it—but it surfaces the debt in pull requests where humans can see it. Gopls also ships these modernizers, so developers get inline suggestions in their editors. That real-time feedback is where the real behavior change happens.

The Go team has hinted at more. They’re exploring custom modernizers that organizations can write for their own internal APIs. Imagine annotating a deprecated helper with //go:fix inline and having every call site updated automatically across a hundred services. That’s not science fiction; it’s in Go 1.26 as a preview.

Closing Thoughts

AI-assisted development is here to stay, and I’m not a pessimist about it. I use these tools daily. But they don’t replace judgment, and they definitely don’t replace good tooling. If anything, AI-generated code makes tools like go fix more essential, not less.

The best codebases I know aren’t the ones using the newest framework. They’re the ones where boring, modern idioms are applied consistently, where a maps.Keys call doesn’t surprise anyone, and where the toolchain enforces standards so humans don’t have to argue about them in code review.

Go 1.26’s modernizers are a perfect example of what Go does best: evolve carefully, automate the boring parts, and let engineers focus on what actually matters.

Run go fix -diff ./... on your biggest repo. I bet you’ll find something.