Generics
Why generics?
Section titled “Why generics?”Before Go 1.18, writing a function that worked on multiple types required either code duplication or interface{} plus type assertions at runtime, losing type safety. Generics solve this: write once, the compiler generates type-specific code and catches misuse at compile time.
For example, a Max function that works for both int and float64 previously required either two identical functions or an interface{} parameter that silently accepted the wrong type. With generics, one function covers both — and the compiler rejects any call with an unsupported type.
Type parameters
Section titled “Type parameters”The syntax for a generic function is func FuncName[T Constraint](args) returnType. The [T Constraint] section is called the type parameter list. T is a placeholder that the compiler substitutes with a concrete type at each call site.
func Max[T Number](a, b T) T { if a > b { return a } return b}At the call site, Go usually infers T from the argument types. You can write Max(3, 7) and the compiler infers T is int — no need to write Max[int](3, 7) explicitly. Explicit type arguments are only needed when inference is ambiguous or when calling with no arguments.
Constraints
Section titled “Constraints”A constraint is an interface that restricts which types may be substituted for a type parameter. Constraints appear after the type parameter name in the [T Constraint] list.
any— the most permissive constraint; equivalent tointerface{}. Allows any type, but limits what operations you can perform on values of typeT(only operations valid for all types: assignment, passing to functions, etc.).comparable— any type that supports==and!=. Use it when your generic function needs to compare values for equality.
You can write custom constraints using interface union syntax:
type Number interface { ~int | ~float64}The ~ prefix means “any type whose underlying type is int or ~float64.” This is the tilde operator: without it, only the exact named type matches; with it, custom types like type Celsius float64 also satisfy ~float64. This makes constraints work naturally with domain-specific type aliases.
Built-in constraints from cmp
Section titled “Built-in constraints from cmp”Go 1.21 introduced the cmp package, which exports cmp.Ordered — the idiomatic constraint for any type that supports <, <=, >, >=, ==. It covers all integer, float, and string types. Using cmp.Ordered is cleaner than writing out the full union ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ... | ~float64 | ~string manually.
import "cmp"
func Min[T cmp.Ordered](a, b T) T { if a < b { return a } return b}For the playground snippet below, cmp.Ordered is replaced by a local Number interface to avoid the external package import — yaegi (the in-browser interpreter) has partial generics support and works most reliably with inline constraints.
Generic types
Section titled “Generic types”You can parameterize structs as well as functions. Declare the type parameter after the type name:
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 } last := s.items[len(s.items)-1] s.items = s.items[:len(s.items)-1] return last, true}Methods on a generic type repeat the type parameter in the receiver — (s *Stack[T]) — but do not add a new constraint. The constraint is fixed once when the type is declared.
package main
import "fmt"
type Number interface { ~int | ~float64}
func Max[T Number](a, b T) T { if a > b { return a } return b}
func Map[T, U any](slice []T, f func(T) U) []U { result := make([]U, len(slice)) for i, v := range slice { result[i] = f(v) } return result}
func Filter[T any](slice []T, pred func(T) bool) []T { var result []T for _, v := range slice { if pred(v) { result = append(result, v) } } return result}
func main() { fmt.Println(Max(3, 7)) fmt.Println(Max[float64](3.14, 2.71))
nums := []int{1, 2, 3, 4, 5} doubled := Map(nums, func(n int) int { return n * 2 }) fmt.Println(doubled)
words := []string{"go", "generics", "fun", "code"} long := Filter(words, func(s string) bool { return len(s) > 3 }) fmt.Println(long)}Loading Go runtime (first run only, ~8 MB)…