Learn Go← Dashboard

Type parameters, constraints, and the comparable / any keywords.

Type Parameters

Before Go 1.18 you'd write a Max function for int, and another for float64, and another for string, because the language couldn't express "any orderable type". Generics fixed that.

A generic function or type has type parameters in square brackets after the name.

Generic functions

func Max[T int | float64 | string](a, b T) T {
    if a > b { return a }
    return b
}

The type parameter list is [T int | float64 | string]. T is the type parameter; int | float64 | string is its constraint — the set of types allowed for T.

go playground
Loading...

You don't usually have to spell out the type — Go infers it from the arguments. When inference doesn't work, write it explicitly: Max[int](3, 5).

Generic types

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

func (s *Stack[T]) Push(v T)  { s.data = append(s.data, v) }
func (s *Stack[T]) Pop() T {
    v := s.data[len(s.data)-1]
    s.data = s.data[:len(s.data)-1]
    return v
}
go playground
Loading...

Stack[string] is a concrete type. You can have Stack[int], Stack[User], whatever you like — each one is its own type.

When to reach for generics

Don't use generics just because you can. Good fits:

  • CollectionsSet[T], Queue[T], LRU[K, V].
  • Algorithms over slices and maps — sort, map, filter, find.
  • Eliminating "T-and-also-T" function pairsMaxInt, MaxFloatMax[T].

Bad fits:

  • "Just in case it might be useful later" — concrete code is easier to read.
  • When an interface would do — interfaces are simpler and don't need stamping.
  • For polymorphic behavior — that's still what interfaces are for.

Go proverb: interface satisfies behavior, generics constrain types.

Why does `func Max[T int | float64](a, b T) T` need a constraint instead of `T any`?