Back to blog

Sunday, August 16, 2026

Go Generics: comparable Constraint Explained With Real Examples

Title card for 'Go Generics: comparable Constraint Explained With Real Examples' with a short code example

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.