ZetCode

Golang comparable built-in type

last modified May 8, 2025

This tutorial explains how to use the comparable built-in type in Go. We'll cover type constraints with practical examples of using comparable in generics.

The comparable type is a predeclared interface in Go that represents all types that can be compared using == and != operators. It was introduced in Go 1.18 to support generics.

In Go, comparable is used as a type constraint to ensure a type parameter supports equality operations. This is essential for writing generic functions that need to compare values.

Basic comparable usage

The simplest use of comparable constrains a type parameter to support equality operations. This example demonstrates basic comparable usage.
Note: comparable includes all comparable types except interfaces.

basic_comparable.go
package main

import "fmt"

func Equal[T comparable](a, b T) bool {
    return a == b
}

func main() {

    fmt.Println(Equal(5, 5))       // true
    fmt.Println(Equal("foo", "bar")) // false
    fmt.Println(Equal(3.14, 3.14))  // true
}

The Equal function works with any comparable type. It demonstrates how comparable enables type-safe equality comparisons in generics.

Using comparable with structs

Struct types are comparable if all their fields are comparable. This example shows how comparable works with custom struct types.

struct_comparable.go
package main

import "fmt"

type Point struct {
    X, Y int
}

func Contains[T comparable](slice []T, value T) bool {
    for _, v := range slice {
        if v == value {
            return true
        }
    }
    return false
}

func main() {

    points := []Point{{1, 2}, {3, 4}, {5, 6}}
    fmt.Println(Contains(points, Point{3, 4})) // true
    fmt.Println(Contains(points, Point{7, 8})) // false
}

The Contains function works with any comparable type, including structs. The Point struct is comparable because its fields are comparable.

Comparable with maps

Map keys must be comparable types. This example shows how comparable helps create generic map utilities.

map_comparable.go
package main

import "fmt"

func MapKeys[K comparable, V any](m map[K]V) []K {
    keys := make([]K, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }
    return keys
}

func main() {

    strMap := map[string]int{"a": 1, "b": 2}
    fmt.Println(MapKeys(strMap)) // [a b]

    intMap := map[int]string{1: "one", 2: "two"}
    fmt.Println(MapKeys(intMap)) // [1 2]
}

The MapKeys function extracts keys from any map type. The K type parameter must be comparable since map keys require comparison operations.

Comparable limitations

Not all types are comparable. This example demonstrates types that don't satisfy the comparable constraint and how to handle them.

limitations.go
package main

import "fmt"

type NonComparable struct {
    Data []int
}

func main() {

    // This would cause a compile-time error:
    // fmt.Println(Equal(NonComparable{}, NonComparable{}))
    
    // Workaround for non-comparable types
    compareSlices := func(a, b []int) bool {
        if len(a) != len(b) {
            return false
        }
        for i := range a {
            if a[i] != b[i] {
                return false
            }
        }
        return true
    }
    
    nc1 := NonComparable{Data: []int{1, 2, 3}}
    nc2 := NonComparable{Data: []int{1, 2, 3}}
    fmt.Println(compareSlices(nc1.Data, nc2.Data)) // true
}

Slices, maps, and functions are not comparable. The example shows how to work around this limitation with custom comparison logic.

Comparable with interfaces

Interfaces can be comparable if their dynamic types are comparable. This example demonstrates the behavior of comparable with interface types.

interface_comparable.go
package main

import "fmt"

type Stringer interface {
    String() string
}

type MyString string

func (ms MyString) String() string {
    return string(ms)
}

func CompareStringers[T Stringer](a, b T) bool {
    return a == b
}

func main() {

    var s1 Stringer = MyString("hello")
    var s2 Stringer = MyString("hello")
    var s3 Stringer = MyString("world")
    
    fmt.Println(CompareStringers(s1, s2)) // true
    fmt.Println(CompareStringers(s1, s3)) // false
}

The CompareStringers function works because the underlying type (MyString) is comparable. Interface comparisons depend on their dynamic types.

Source

Go language specification

This tutorial covered the comparable built-in type in Go with practical examples of using it in generic functions and type constraints.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Golang tutorials.