Go 1.24: What’s New and Worth Using

Go 1.24: What's New and Worth Using

Go 1.24 was released in February 2025, following the Go team’s commitment to two releases per year. Like most Go releases, it focuses on incremental improvements rather than dramatic changes. The language design philosophy of stability means there are no breaking changes — code that compiled in Go 1.0 still compiles today.

That said, 1.24 has several additions worth knowing about and adopting, particularly the iterator improvements, tool dependency management, and continued generics enhancements.

Range Over Functions (Production Ready)

Go 1.22 introduced experimental support for ranging over function iterators, and 1.23 made it non-experimental. Go 1.24 adds improvements to the iterator pattern and standard library support for it.

The core idea: you can now range over any function that has the right signature:

// Iterator function signature for a simple sequence
func Fibonacci() func(yield func(int) bool) {
    return func(yield func(int) bool) {
        a, b := 0, 1
        for {
            if !yield(a) {
                return
            }
            a, b = b, a+b
        }
    }
}

func main() {
    // Range over the iterator
    for n := range Fibonacci() {
        if n > 100 {
            break
        }
        fmt.Println(n)
    }
}

Key-value iterators (two-value range):

// Iterator producing key-value pairs
func EnumerateLines(r io.Reader) func(yield func(int, string) bool) {
    return func(yield func(int, string) bool) {
        scanner := bufio.NewScanner(r)
        lineNum := 0
        for scanner.Scan() {
            if !yield(lineNum, scanner.Text()) {
                return
            }
            lineNum++
        }
    }
}

func main() {
    f, _ := os.Open("data.txt")
    defer f.Close()

    for lineNum, line := range EnumerateLines(f) {
        fmt.Printf("%d: %s\n", lineNum, line)
    }
}

The standard library’s slices, maps, and iter packages now expose iterators:

import (
    "maps"
    "slices"
)

m := map[string]int{"a": 1, "b": 2, "c": 3}

// Iterate over sorted keys
for k := range slices.Sorted(maps.Keys(m)) {
    fmt.Println(k, m[k])
}

Tool Dependencies Simplified

Before 1.24, managing tools (linters, code generators, etc.) in a Go module required a hacky tools.go file:

// The old way (pre-1.24): tools.go
//go:build tools

package tools

import (
    _ "github.com/golangci/golangci-lint/cmd/golangci-lint"
    _ "golang.org/x/tools/cmd/stringer"
)

Go 1.24 adds a tool directive to go.mod:

// go.mod
module github.com/mycompany/myproject

go 1.24

require (
    github.com/gin-gonic/gin v1.9.1
)

tool (
    github.com/golangci/golangci-lint/cmd/golangci-lint
    golang.org/x/tools/cmd/stringer
)

Install and run tools:

# Install all tools declared in go.mod
go get tool

# Run a tool
go tool golangci-lint run
go tool stringer -type=Color

This is a cleaner approach to the common pattern of wanting reproducible tool versions alongside module dependencies.

The testing/synctest Package

Go 1.24 introduces an experimental testing/synctest package that helps test concurrent code that involves timers, deadlines, and time.Sleep. The problem with testing concurrent code has always been: you don’t want tests that actually wait 10 seconds for a timeout to fire.

// Testing time-dependent code without actual delays
func TestRetryWithTimeout(t *testing.T) {
    synctest.Run(func() {
        ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
        defer cancel()

        callCount := 0
        result, err := RetryWithBackoff(ctx, func() error {
            callCount++
            if callCount < 3 {
                return errors.New("not ready yet")
            }
            return nil
        })

        // Advance fake time to trigger the retry logic
        synctest.Wait()

        if err != nil {
            t.Errorf("expected success, got %v", err)
        }
        _ = result
    })
}

This is experimental and the API may change, but it addresses a real pain point — testing code that uses time.After, time.Sleep, or context deadlines without making your test suite slow.

Performance: Improved Garbage Collector

Go 1.24 includes ongoing improvements to the garbage collector, particularly for programs with large heaps and many goroutines. The specific improvements:

  • Reduced GC latency for programs with high allocation rates
  • Better CPU utilization during GC cycles
  • Improved GOGC and GOMEMLIMIT interaction for memory-constrained environments

For Kubernetes workloads, the GOMEMLIMIT feature (available since 1.19, improved in each release) is particularly important:

// Set in your deployment, or in code:
import "runtime/debug"

func init() {
    // Set memory limit to 90% of container limit
    // to avoid OOMKill
    debug.SetMemoryLimit(900 * 1024 * 1024) // 900MB for a 1GB container
}
# Better approach: set via environment variable in Kubernetes
env:
- name: GOMEMLIMIT
  valueFrom:
    resourceFieldRef:
      resource: limits.memory
      divisor: "1"  # bytes

Setting GOMEMLIMIT to your container memory limit prevents Go’s GC from being too conservative and then getting OOMKilled anyway — a common issue with Go services in Kubernetes.

The weak Package: Weak References

Go 1.24 adds weak.Pointer[T] for weak references — references that don’t prevent garbage collection:

import "weak"

type Cache struct {
    data map[string]weak.Pointer[[]byte]
    mu   sync.Mutex
}

func (c *Cache) Get(key string) ([]byte, bool) {
    c.mu.Lock()
    defer c.mu.Unlock()

    if wp, ok := c.data[key]; ok {
        if value := wp.Value(); value != nil {
            return *value, true
        }
        // Value was garbage collected, remove stale entry
        delete(c.data, key)
    }
    return nil, false
}

Weak references are useful for caches where you want entries to be garbage collected if nothing else holds a reference to them. Previously you’d use sync.Pool (which has different semantics) or manage lifecycle manually.

Finalization with runtime.AddCleanup

A more powerful replacement for runtime.SetFinalizer — which had footguns around being called only once and being tied to GC timing. runtime.AddCleanup allows multiple cleanup functions per object and is more predictable:

type Resource struct {
    handle int
}

func NewResource() *Resource {
    r := &Resource{handle: openNativeResource()}
    runtime.AddCleanup(r, func(handle int) {
        closeNativeResource(handle)
    }, r.handle)
    return r
}

Upgrading to Go 1.24

# Download and install
wget https://go.dev/dl/go1.24.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.24.linux-amd64.tar.gz

# Verify
go version
# go version go1.24 linux/amd64

# Update your go.mod
go mod tidy

Check for compatibility issues:

# Run all tests after upgrade
go test ./...

# Check for deprecated usage
go vet ./...

Summary of What’s Worth Adopting Now

Feature Value Stability
Range over iterators High for library code Stable
go.mod tool directive Medium — cleaner tooling Stable
testing/synctest High for concurrent code tests Experimental
GOMEMLIMIT in K8s High — prevents OOMKill Stable (since 1.19)
weak.Pointer Medium — specific use cases Stable

Conclusion

Go 1.24 continues the pattern of incremental, practical improvements. The iterators story is now fully complete and worth using in any library code that exposes sequences. The tool directive is an immediate improvement if you’ve been fighting tools.go conventions.

If you’re writing Go services for Kubernetes, the most impactful change from recent Go versions is probably GOMEMLIMIT — set it in all your deployments if you haven’t already. It’s one of the simplest fixes for Go services that mysteriously get OOMKilled.

Stay current. Go’s backward compatibility means upgrades are safe, and each release brings measurable performance improvements.

Share this post: LinkedIn Reddit WhatsApp Mastodon
Jesse Borden

Jesse Borden

Software Engineer with an interest in hands on learning

I have several years of professional Information Technology (IT) experience leading staff and projects within the Department of War (DOW). I have managed Service Desk, Web Application Development, and System Administration teams. My two greatest passions are learning and conti...