Sitemap
Golang +

Golang +

#golang news, interesting stuff, blog links and golang jobs.

Go 1.25 & 1.26: Practical Features That Actually Make Your Code Better

6 min readMay 29, 2026

--

Press enter or click to view image in full size
Practical Features That Actually Improve Your Go Code

If you’ve been writing Go for a while, you know the drill: each release brings solid, incremental improvements rather than flashy syntax overhauls. Go 1.25 and 1.26 continue that tradition — but they also quietly deliver some genuinely exciting changes that can simplify your code, eliminate entire classes of bugs, and make your programs faster without you lifting a finger.

Let’s walk through the most impactful features you can start using today.

📦 What’s in This Post

  • Cleaner pointer initialization — new now accepts expressions
  • Recursive generics — Self-referential type constraints finally work
  • JSON v2 — A long-awaited, safer JSON package
  • Zero-effort performance — Stack-allocated slices and Green Tea GC
  • Smart tooling — go fix modernizers keep your code fresh

🧱 Language Features: Smaller Surface Area, More Expressiveness

1. new Now Accepts Expressions (Go 1.26)

Before Go 1.26, new only accepted a type as its operand. Need a pointer to an initialized value? You had to write two lines:

// Before Go 1.26 — two lines, extra variable
x := int64(300)
ptr := &x

Now you can do it in one clean expression:

// Go 1.26+ — one line, crystal clear
ptr := new(int64(300))

This shines when working with optional fields in serialization. Consider a Person struct where Age is optional, represented by a pointer:

import "encoding/json"
type Person struct {
Name string `json:"name"`
Age *int `json:"age"` // nil if age unknown
}
func personJSON(name string, born time.Time) ([]byte, error) {
return json.Marshal(Person{
Name: name,
Age: new(yearsSince(born)), // clean and expressive
})
}
func yearsSince(t time.Time) int {
return int(time.Since(t).Hours() / (365.25 * 24))
}

No more declaring temporary variables just to take their address. This small quality-of-life improvement ripples through any codebase that uses pointers for optional values.

2. Recursive Generics: Self-Referential Type Constraints (Go 1.26)

This one is a big deal for library authors. Until Go 1.26, you could not define a generic constraint that referred to itself. The classic example that always failed:

// This would NOT compile before Go 1.26
type Adder[A Adder[A]] interface {
Add(A) A
}

Now it works. The restriction has been lifted, unlocking genuinely expressive generic interfaces.

CRDTs and Mergeable Types

Distributed systems often need types that know how to merge with themselves:

type Mergeable[T Mergeable[T]] interface {
Merge(other T) T
}
// A concrete type that implements Mergeable
type ServerConfig struct {
Host string
Port int
Timeout int
}
func (s ServerConfig) Merge(other ServerConfig) ServerConfig {
return ServerConfig{
Host: other.Host,
Port: other.Port,
Timeout: max(s.Timeout, other.Timeout),
}
}
// Generic merge function that works with any Mergeable type
func MergeAll[T Mergeable[T]](configs []T) T {
result := configs[0]
for _, c := range configs[1:] {
result = result.Merge(c)
}
return result
}

Tree Structures

The same pattern makes tree definitions natural and type-safe:

type TreeNode[T TreeNode[T]] interface {
GetChildren() []T
}
func CountNodes[T TreeNode[T]](node T) int {
count := 1
for _, child := range node.GetChildren() {
count += CountNodes(child)
}
return count
}

This is not just theory. It is the kind of expressiveness that makes Go’s generics feel complete.

3. Range Functions Are Stable (Go 1.25)

Range functions, which were experimental in Go 1.24, are now production-ready. They let you use custom functions directly in range expressions:

// A function that yields values to a range loop
func Backwards(s []int) func(func(int) bool) {
return func(yield func(int) bool) {
for i := len(s) - 1; i >= 0; i-- {
if !yield(s[i]) {
break
}
}
}
}
// Use it directly
func processReversed(items []int) {
for v := range Backwards(items) {
// Process v in reverse order
}
}

This is Go’s take on iterators: no magic, just functions that respect control flow with the yield pattern.

⚡ Performance That Does Not Need Your Help

1. Automatic Small Slice Stack Allocation (Go 1.25+)

Here is the kind of optimization every Go developer loves: faster code without changing a single line. Starting in Go 1.25, the compiler automatically allocates the backing store for small slices, up to 32 bytes, on the stack instead of the heap.

func processTasks(c chan Task) {
tasks := make([]Task, 0, 10) // <= 32 bytes? Stack-allocated.
for t := range c {
tasks = append(tasks, t)
}
processAll(tasks)
}

No heap allocation, no GC pressure, no code changes required.

Get Golang +’s stories in your inbox

Join Medium for free to get updates from this writer.

Go 1.26 takes this further with smarter append behavior. The first few append operations now use a stack-allocated buffer:

var tasks []Task          // empty slice
for t := range c {
tasks = append(tasks, t) // First appends use stack buffer
}

If the slice outgrows the small stack buffer, it transitions to heap allocation seamlessly. Your code stays simple; the compiler handles the optimization.

2. Green Tea GC Is Now Default (Go 1.26)

The experimental Green Tea garbage collector graduates to default status in Go 1.26. This GC focuses on better locality and CPU scalability, delivering lower pause times without requiring any configuration changes. For high-allocation workloads, you will notice the difference.

3. cgo Overhead Reduced by Around 30%

If your project calls C code, Go 1.26 brings welcome relief. The baseline cgo overhead is reduced by approximately 30%, making mixed-language projects significantly more efficient.

4. Container-Aware GOMAXPROCS (Go 1.25)

Before Go 1.25, GOMAXPROCS defaulted to the host CPU count. That was a problem in containerized environments with CPU quotas. Go 1.25 now reads Linux cgroup limits and adjusts GOMAXPROCS accordingly, reducing the thread thrashing that plagued Go in containers.

🛠️ Standard Library Improvements

1. encoding/json/v2 Experimental in Go 1.25

The encoding/json package has served Go well for 15 years, but it has accumulated quirks. Go 1.25 introduces an experimental encoding/json/v2, behind the GOEXPERIMENT=jsonv2 build tag, that fixes long-standing issues.

What is improved:

  • Strict UTF-8 validation: no more silent corruption
  • Duplicate object keys rejected by default: better security hardening
  • Nil slices and maps marshal to [] and {} instead of null: better interoperability
  • Case-sensitive unmarshaling: fewer surprising mismatches

To try it:

// Build with: GOEXPERIMENT=jsonv2 go build
import "encoding/json/v2"

This is the first major JSON overhaul since Go 1.0, and it is worth experimenting with for new projects.

2. testing/synctest Now Stable (Go 1.25)

Testing concurrent code is historically painful. Goroutine interleaving and timeouts make tests flaky. Go 1.25 graduates testing/synctest to stable:

import "testing/synctest"
func TestCounterConcurrency(t *testing.T) {
st := synctest.New()
defer st.Done()

c := &Counter{}
for i := 0; i < 10; i++ {
st.Go(func() { c.Inc() })
}
st.Run()

// Deterministic execution: always works, no flakes
if c.N() != 10 {
t.Fatal("race condition detected")
}
}

Inside a synctest bubble, time is virtualized and goroutines are scheduled deterministically. No more time.Sleep in tests, no more CI flakiness.

🧰 Tooling: go fix Modernizers

The go fix command has been completely rewritten in Go 1.26 and now includes dozens of automatic fixers called modernizers that update your code to use newer language features and library APIs.

Want to automatically convert all your two-line pointer initializations to the new new(expr) form? go fix handles it.

Want to find functions that could use range iterators? go fix finds and updates them.

# Automatically modernize your entire codebase
go fix ./...

The new go fix is built on the same analysis framework as go vet, so it is production-ready and conservative. It will not change program behavior; it only modernizes idioms.

🪵 Smaller Binaries and Better Debugging

  • DWARF v5 by default: smaller binaries and faster linking for debug builds
  • go mod ignore directive: exclude test data or legacy code from builds without removing it from the module archive
  • go version -m -json: get structured build information for security audits

🔄 Migration Guide

Go’s compatibility promise holds firm: all your existing code continues to compile. Upgrading is straightforward:

# Install Go 1.26
go install golang.org/dl/go1.26.0@latest
go1.26.0 download
# Verify the installation
go1.26.0 version

Then run the new modernizers to automatically adopt language features:

# In your project root
go fix ./...

The modernizers will suggest safe changes. Review them and commit the improvements. Your codebase will be cleaner, safer, and faster with minimal effort.

📝 Summary

Go 1.25 and 1.26 embody what makes Go great: thoughtful improvements that respect your time, strong defaults that protect you from mistakes, and tooling that scales with your project.

Upgrade today, run go fix, and let the compiler do the heavy lifting. Your future self will thank you.

Happy coding! 🚀 and follow me on x.com/golangch

--

Golang +
Golang +

Written by Golang +

#golang news, interesting stuff, blog links and golang jobs.