Skip to content

Generics

Before Go 1.18, writing a function that worked over any type meant either code duplication or falling back to interface{} and losing type safety. Generics fix that.

func Map[T, U any](items []T, fn func(T) U) []U {
result := make([]U, len(items))
for i, item := range items {
result[i] = fn(item)
}
return result
}

T and U are type parameters — the compiler infers them from the arguments at each call site, so callers never write Map[int, string](...) explicitly unless inference fails.

any (alias for interface{}) allows literally any type, but you’ll often want to restrict to types that support certain operations:

type Number interface {
int | int64 | float64
}
func Sum[T Number](items []T) T {
var total T
for _, item := range items {
total += item
}
return total
}

The standard library’s constraints-style interfaces live in golang.org/x/exp/constraints, and the built-in cmp.Ordered constraint (from the cmp package) covers anything you can compare with <.

Type parameters aren’t just for functions — types can have them too:

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

Generics shine for containers and algorithms (stacks, sets, Map/Filter/Reduce helpers) that are naturally type-agnostic. For business logic, a plain interface is usually clearer — don’t reach for type parameters just because you can.