— notes
The comparable Constraint in Go Generics
On this page
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 --> Cany 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"]
endThe real value of comparable is catching errors at compile time instead of letting them slip into production.