Sunday, August 16, 2026
Go Generics: comparable Constraint Explained With Real Examples
Posted by

comparable is the predeclared constraint that lets a type parameter use == and !=. That is all. It is not ordering. min[T comparable] cannot use <. Slices, maps, and functions never satisfy it. A struct fails if any field fails. Since Go 1.20, interface types (including any) satisfy comparable so generic maps can match map[any]string, but putting a slice inside that any key still panics when the map hashes it.
The failure
package main
func Equal[T comparable](a, b T) bool {
return a == b
}
func Min[T comparable](a, b T) T {
if a < b { // invalid operation: a < b (operator < not defined on T)
return a
}
return b
}
func main() {
Equal([]int{1}, []int{1}) // []int does not satisfy comparable
}
Same story for map keys: map[[]byte]int does not compile. type HasSlice struct{ Xs []int } is not comparable either, one slice field poisons the struct.
Why this happens
Go's == is defined for a closed set: numbers, strings, bools, pointers, channels, arrays of comparable elements, structs of comparable fields, and interfaces. Interfaces compare by dynamic type then dynamic value. If the value is a slice, that comparison panics.
A type parameter has no operators until the constraint allows them. any allows none of ==. comparable allows == / != only.
Go 1.18-1.19 treated comparable as strictly comparable (no interface types in the type). Then type M[K comparable] map[K]V could not be instantiated as M[any], even though map[any]string was always legal. Go 1.20 aligned them: any spec-comparable type satisfies the constraint. Runtime panics on non-comparable dynamic values remain.
comparable cannot be a variable type. It exists only as a constraint.
The fix
Equality and map keys, use comparable:
func Contains[T comparable](s []T, x T) bool {
for _, v := range s {
if v == x {
return true
}
}
return false
}
type Set[T comparable] map[T]struct{}
func (s Set[T]) Has(x T) bool {
_, ok := s[x]
return ok
}
Ordering, not comparable. Use cmp.Ordered (Go 1.21+) or a union:
func Min[T interface{ ~int | ~int64 | ~float64 | ~string }](a, b T) T {
if a < b {
return a
}
return b
}
Custom equality (case-insensitive strings, float epsilon), pass a function. slices.ContainsFunc is that pattern. comparable will not help.
Keep map keys strictly comparable (string, int, structs of those) unless you know every interface value you store is comparable. map[any]T plus a []byte key is a delayed crash.