Generics

Go added generics in 1.18. Type parameters let you write functions and types that work with multiple types while keeping full type safety.

Type Parameters

func Map[T any, U any](s []T, f func(T) U) []U {
    result := make([]U, len(s))
    for i, v := range s {
        result[i] = f(v)
    }
    return result
}

// Usage - types are inferred
names := Map(users, func(u User) string { return u.Name })
lengths := Map(names, func(s string) int { return len(s) })

The [T any, U any] syntax declares type parameters. any means no constraint (any type is accepted).

Constraints

Constraints limit which types can be used as type arguments.

Built-in Constraints

import "cmp"

// comparable: types that support == and !=
func Contains[T comparable](s []T, target T) bool {
    for _, v := range s {
        if v == target {
            return true
        }
    }
    return false
}

// cmp.Ordered: types that support <, >, <=, >=
func Max[T cmp.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

Custom Constraints

Define constraints as interfaces:

type Number interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
    ~float32 | ~float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

The ~ prefix means “underlying type”. ~int matches int and any named type whose underlying type is int (like type UserID int).

Method Constraints

type Stringer interface {
    String() string
}

func JoinStrings[T Stringer](items []T, sep string) string {
    parts := make([]string, len(items))
    for i, item := range items {
        parts[i] = item.String()
    }
    return strings.Join(parts, sep)
}

Generic Types

type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(v T) {
    s.items = append(s.items, v)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    v := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return v, true
}

// Usage
s := &Stack[int]{}
s.Push(1)
s.Push(2)
v, _ := s.Pop() // 2

Self-Referential Generic Types (Go 1.26)

Go 1.26 allows types to refer to themselves in generic constraints, enabling patterns like fluent builders:

type Builder[Self any] interface {
    SetName(string) Self
}

type UserBuilder struct {
    name  string
    email string
}

func (b UserBuilder) SetName(n string) UserBuilder {
    b.name = n
    return b
}

Core Types Removed (Go 1.25)

Go 1.25 removed the “core type” restriction that previously blocked many useful generic patterns. Interfaces with multiple terms now work in more places:

// This was not allowed before Go 1.25
type Addable interface {
    ~int | ~float64 | ~string
}

func Add[T Addable](a, b T) T {
    return a + b // works for int, float64, and string concatenation
}

The slices and maps Packages

The standard library includes generic utility packages:

import "slices"

nums := []int{3, 1, 4, 1, 5, 9}

slices.Sort(nums)                  // [1, 1, 3, 4, 5, 9]
slices.Contains(nums, 4)           // true
slices.Index(nums, 5)              // 4
idx, found := slices.BinarySearch(nums, 4)

sorted := slices.SortedFunc(slices.Values(nums), func(a, b int) int {
    return b - a // descending
})
import "maps"

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

keys := slices.Collect(maps.Keys(m))     // ["a", "b", "c"] (unordered)
values := slices.Collect(maps.Values(m)) // [1, 2, 3] (unordered)
clone := maps.Clone(m)

When to Use Generics

Good uses:

  • Data structures (stacks, queues, trees, sets)
  • Utility functions (map, filter, reduce, contains)
  • Type-safe collections
  • Algorithms that work on ordered/comparable types

Avoid generics when:

  • An interface would work just as well (prefer io.Reader over T with a Read constraint)
  • You only use one or two concrete types (just write two functions)
  • The generic version is harder to read than the concrete version

Tip: If you are writing [T any] and never constraining T, ask yourself if any (the non-generic version) would work just as well.

Exercises

  1. Generic Set: Implement Set[T comparable] with Add, Contains, Remove, Len, and Values methods.

  2. Result type: Implement Result[T any] that holds either a value or an error, with Ok(T), Err(error), Unwrap() (T, error), and Map(func(T) U) Result[U] methods.

  3. Pipeline: Write a Pipeline[T any] that chains transformation functions func(T) T and applies them in order.

Next: Concurrency Patterns | Interfaces