— notes

The comparable Constraint in Go Generics

2 min read#go
On this page
  1. Why comparable?
  2. Constraint Hierarchy
  3. any vs. interface{}
  4. Real-World Scenarios
  5. Compile-Time vs Runtime

While working on a generic function in Go, I once encountered this error: ‘incomparable types in type set’.

It led me to dig deeper into the comparable constraint. With this post, I’ll walk you through what comparable is, why it’s useful, and how you can leverage it for cleaner, type-safe Go code.

Why comparable?

comparable is a constraint that ensures a type supports equality comparisons (== and !=).

In Go, only certain types are inherently comparable: primitive types like int, float64 and string, but exclude types like slices, maps, and functions.

type Array[T comparable] struct {
    data []T
}

func (a *Array[T]) Contains(value T) bool {
    for _, v := range a.data {
        if v == value {
            return true
        }
    }
    return false
}

Constraint Hierarchy

graph TD
    A["any\n(all types)"]
    B["comparable\n(supports == and !=)"]
    C["constraints.Ordered\n(supports < > <= >=)"]
    A --> B
    B --> C

any vs. interface{}

any is simply an alias for interface{}. While functionally identical, any better aligns with the intentions of generics and improves readability.

func PrintValues[T any](values []T) {
    for _, v := range values {
        fmt.Println(v)
    }
}

Real-World Scenarios

1. Deduplication with comparable

func RemoveDuplicates[T comparable](input []T) []T {
    seen := make(map[T]bool)
    result := []T{}
    for _, v := range input {
        if !seen[v] {
            seen[v] = true
            result = append(result, v)
        }
    }
    return result
}

Compile-Time vs Runtime

graph LR
    subgraph "Using any (unsafe)"
        A1["func Contains\n[T any]"] --> B1["Pass []int\nas T"]
        B1 --> C1["Runtime Panic\n== on slice"]
    end
    subgraph "Using comparable (safe)"
        A2["func Contains\n[T comparable]"] --> B2["Pass []int\nas T"]
        B2 --> C2["Compile Error\n[]int not comparable"]
    end

The real value of comparable is catching errors at compile time instead of letting them slip into production.