Go 1.26: The Changes I Would Actually Use
A practical look at the Go 1.26 changes that affect ordinary application code: new expressions, modernizers, profiling, module defaults, and the new garbage collector.
Most Go releases do not require a migration project. That is one of the language's strengths. Go 1.26 is the same: existing programs should keep working, while a few small changes make everyday code and maintenance nicer. I would upgrade normally, run the existing tests, and spend my attention on the features that can simplify real code rather than trying to prove that a newer runtime is newer.
Optional values no longer need a helper
The built-in new function can now take an expression. This sounds tiny, but it removes one of those generic pointer helpers that appears in many API clients and configuration packages. It is especially handy when nil means “not supplied” and a pointer to the zero value means “supplied as zero.” JSON PATCH payloads, generated SDK types, and protobuf messages commonly need that distinction.
I would use this at the boundary where the optional value is constructed. I would not turn ordinary value fields into pointers just because the syntax is shorter. A pointer still adds another state to the model, so nil should have a clear meaning.
type UpdateUser struct {
DisplayName *string `json:"display_name,omitempty"`
LoginCount *int `json:"login_count,omitempty"`
}
payload := UpdateUser{
DisplayName: new(strings.TrimSpace(input.Name)),
LoginCount: new(0), // explicitly reset, rather than omit
}go fix is useful again when the diff stays reviewable
Go 1.26 rewrites go fix around the same analysis framework used by go vet and gives it a set of modernizers. These can update older idioms and standard-library calls while preserving behaviour. That makes it a sensible maintenance tool, not a command I would run blindly across a repository and merge as one enormous change.
My approach is to start with a clean branch, run the tests first, execute go fix on a small package, and review the actual diff. Mechanical updates get their own pull request, separate from product work. If a suggested modernization makes the code less clear for the team, there is no prize for accepting it.
go test ./...
go fix ./internal/billing/...
git diff --stat
git diff
go test ./...The runtime improvement should mostly be uneventful
Green Tea, the garbage collector previewed in Go 1.25, is enabled by default. The release notes say the largest improvements are in programs that spend meaningful time scanning many small objects, so not every service will notice the same change. For a normal application, I would not create a benchmark programme around this. Existing load tests, error monitoring, and a routine staged deployment are enough.
There is a temporary nogreenteagc opt-out for unexpected behaviour, but the Go team expects to remove it in 1.27. I would treat that as a short diagnostic escape hatch, not a tuning recommendation. If an upgrade exposes a problem, keep the evidence and report it upstream instead of carrying an invisible runtime switch forever.
Small tooling changes are easier to notice than to debug later
New modules created with a Go 1.26 toolchain default to go 1.25.0 in go.mod. This is intentional: it encourages compatibility with currently supported Go versions. If a project genuinely requires a 1.26 language or library feature, set that version deliberately rather than assuming go mod init did it.
The pprof web interface now opens on a flame graph, while the previous graph view remains available from the menu. Also, go tool doc has been removed in favour of go doc. Neither is a major migration concern, but both are worth knowing before a familiar command or screen appears different during debugging.
A deliberately small upgrade plan
I would update the local toolchain and CI image, verify go.mod and go.sum, run tests plus go vet, and rebuild the application image. Services still deserve their normal staging and production rollout, especially when cgo or unusual runtime settings are involved, but that is ordinary release hygiene. It is not a reason to turn the upgrade into a research project.
Only after the toolchain change is stable would I use new syntax or run modernizers. Keeping those diffs separate makes a rollback obvious and lets reviewers judge code changes on their own merits. Most teams should be able to adopt 1.26 with one small infrastructure change and a few optional cleanups.
Use this in practice
- Update local and CI toolchains together
- Run tests and go vet before changing application code
- Check the go directive in newly created modules
- Use new(expr) only when nil has a real meaning
- Review go fix output in small diffs
- Roll services out through the normal staged path
