Go release notes, distilled

What's new in Go, 1.24 → 1.27

Four releases, two years of changes, and most of them you can ignore. These are the three per release that change how you write Go day to day, each with code you can paste and run. Newest first.

Go 1.27 · August 2026

Methods get their own type parameters, and JSON gets a v2.

  1. 01Generic methods

    A method can now declare type parameters of its own, not just use its receiver's. Helpers that had to be package-level functions can live on the type. The standard library already uses it: (*rand.Rand).N in math/rand/v2. Interface methods still can't be generic.

    stack.go
    1type Stack[T any] struct{ items []T }
    2
    3func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
    4
    5// Map declares its own type parameter U.
    6func (s *Stack[T]) Map[U any](f func(T) U) []U {
    7 out := make([]U, 0, len(s.items))
    8 for _, v := range s.items {
    9 out = append(out, f(v))
    10 }
    11 return out
    12}
    13
    14var s Stack[int]
    15s.Push(1)
    16s.Push(2)
    17fmt.Printf("%q\n", s.Map(strconv.Itoa)) // ["1" "2"]
  2. 02encoding/json/v2 is a real package

    The JSON rewrite that was behind GOEXPERIMENT=jsonv2 in 1.25 is now a standard package, with stricter defaults. Two that change real programs: a nil slice encodes as [] instead of null, and object keys match struct fields case-sensitively. The same code under v1 prints "tags":null and fills Name with "x".

    json.go
    1import "encoding/json/v2"
    2
    3type Item struct {
    4 Name string `json:"name"`
    5 Tags []string `json:"tags"`
    6}
    7
    8b, _ := json.Marshal(Item{Name: "heap"})
    9fmt.Println(string(b)) // {"name":"heap","tags":[]}
    10
    11var it Item
    12_ = json.Unmarshal([]byte(`{"Name":"x"}`), &it)
    13fmt.Printf("%q\n", it.Name) // "" ("Name" no longer matches "name")
  3. 03uuid in the standard library

    A new uuid package generates and parses RFC 9562 UUIDs, so one of the most common third-party imports is gone. uuid.New() is the general-purpose choice; uuid.NewV7() gives time-ordered IDs, which index well as database keys.

    id.go
    1import "uuid"
    2
    3a, b := uuid.NewV7(), uuid.NewV7()
    4fmt.Println(len(a.String()), a.String() < b.String()) // 36 true: v7 sorts by time

Full list: Go 1.27 release notes.

Go 1.26 · February 2026

Smaller syntax wins, and a new garbage collector by default.

  1. 01new(expr)

    new now takes an expression as well as a type, so a pointer to a value is one call. The everyday case: optional fields as pointers, which used to need a temporary variable or a helper like func ptr[T any](v T) *T.

    person.go
    1type Person struct {
    2 Name string `json:"name"`
    3 Age *int `json:"age,omitempty"`
    4}
    5
    6p := Person{Name: "Ada", Age: new(36)}
    7b, _ := json.Marshal(p)
    8fmt.Println(string(b)) // {"name":"Ada","age":36}
  2. 02errors.AsType

    A generic, type-safe errors.As: no target variable declared up front, no pointer-to-a-pointer, and the match is scoped to the if.

    open.go
    1_, err := os.Open("/no/such/file")
    2
    3// before:
    4// var pe *fs.PathError
    5// if errors.As(err, &pe) { ... }
    6
    7if pe, ok := errors.AsType[*fs.PathError](err); ok {
    8 fmt.Println("failed path:", pe.Path) // failed path: /no/such/file
    9}
  3. 03Green Tea GC is on by default

    The garbage collector that was an experiment in 1.25 is now the default. It marks and scans small objects with better memory locality, and the release notes expect 10–40% less GC overhead in programs that collect a lot. Nothing to change in your code; opt out with GOEXPERIMENT=nogreenteagc if you measure a regression.

Full list: Go 1.26 release notes.

Go 1.25 · August 2025

Concurrency gets easier to write and to test.

  1. 01sync.WaitGroup.Go

    wg.Go(f) does Add(1), starts the goroutine and calls Done for you, which removes the classic bug of calling Add inside the goroutine. Combined with per-iteration loop variables (Go 1.22), the closure below is safe as written.

    squares.go
    1var wg sync.WaitGroup
    2results := make([]int, 3)
    3for i := range 3 {
    4 wg.Go(func() { results[i] = i * i })
    5}
    6wg.Wait()
    7fmt.Println(results) // [0 1 4]
  2. 02testing/synctest

    Test time-dependent concurrent code without real waiting. Inside synctest.Test, goroutines run in a bubble with a fake clock that jumps forward whenever every goroutine is blocked. The test below sleeps for ten minutes and finishes in milliseconds.

    expiry_test.go
    1func TestExpiry(t *testing.T) {
    2 synctest.Test(t, func(t *testing.T) {
    3 start := time.Now()
    4 time.Sleep(10 * time.Minute) // the fake clock advances instantly
    5 if got := time.Since(start); got != 10*time.Minute {
    6 t.Fatalf("elapsed %v", got)
    7 }
    8 })
    9}
  3. 03Container-aware GOMAXPROCS

    On Linux, GOMAXPROCS now defaults to the lower of the machine's CPUs and the container's cgroup CPU limit, and updates if the limit changes. A service limited to 2 CPUs on a 64-core node no longer runs 64 threads' worth of parallelism into throttling. No code change.

Full list: Go 1.25 release notes.

Go 1.24 · February 2025

A new map, generic aliases, and honest benchmarks.

  1. 01Maps are Swiss Tables

    The built-in map was reimplemented as a Swiss Table: slots in groups of eight, each with a one-byte control word. A lookup checks a whole group's tags in one operation and compares keys only where a tag matches. Same API, faster maps; step through a lookup:

    One map lookup in a Swiss Table
    m["gopher"]→ hash →H1 = group 1H2 = 0x5a
    group 0
    1. 0x12
      rust
    2. empty
      ·
    3. 0x40
      zig
    4. empty
      ·
    5. empty
      ·
    6. 0x07
      c
    7. empty
      ·
    8. empty
      ·
    group 1
    1. 0x5a
      golang
    2. 0x21
      java
    3. empty
      ·
    4. 0x33
      kotlin
    5. 0x5a
      gopher
    6. empty
      ·
    7. 0x0c
      ocaml
    8. deleted
      ·
    1 / 4

    Hash the key once. hash("gopher") is split in two: the high bits (H1) choose a group, the low 7 bits (H2) are a tag.

    Illustrative values. Before Go 1.24, a map was buckets of 8 with overflow chains, and a lookup compared the tophash of each slot one at a time.
  2. 02Generic type aliases

    A type alias can take type parameters, like a defined type. Useful for short names over generic types without creating a new, distinct type.

    set.go
    1type Set[T comparable] = map[T]struct{}
    2
    3seen := Set[string]{}
    4for _, w := range []string{"go", "rust", "go"} {
    5 seen[w] = struct{}{}
    6}
    7fmt.Println(len(seen)) // 2
  3. 03testing.B.Loop

    for b.Loop() replaces for i := 0; i < b.N; i++. Setup before the loop runs once and is not timed, and the compiler can't optimise away the work inside it, which made the old form easy to get wrong.

    sum_test.go
    1func BenchmarkSum(b *testing.B) {
    2 xs := make([]int, 1000) // setup: runs once, not timed
    3 for b.Loop() {
    4 sum(xs)
    5 }
    6}

Full list: Go 1.24 release notes.

Learning DSA in current Go

Reach For teaches data structures, algorithms and system design for interviews in modern Go: every code example in the course is compiled and run against Go 1.27 before it ships. Start with the free modules, or see why the course uses Go.