Tutorial
Golang Compare: A Practical Guide to Equality, Ordering, and Deep Inspection
Master Go's comparison semantics: when == works, why slices and maps don't, and production-tested patterns for safe equality checks across types.
After more than 25 years building backend systems, comparison bugs are among the most insidious production issues you’ll encounter. They’re quiet, they’re contextual, and Go’s type system makes them both safer and more surprising than most languages you’ve probably worked with.
Why Comparison in Go Is Not as Simple as ==
Go gives you a statically typed, compiled language where the rules for comparing values are strict and deliberate. If you’re coming from Python or JavaScript — where == does whatever it feels like — you’ll hit unexpected behaviour quickly.
The core distinction:
- Comparable types support
==and!=directly. - Non-comparable types (slices, maps, functions) do not.
- Ordered types support
<,>,<=,>=.
The compiler enforces this immediately. That’s a feature.
Comparing Primitive Types
For integers, floats, strings, and booleans, == works as expected:
a := 42
b := 42
fmt.Println(a == b) // true
x := "hello"
y := "hello"
fmt.Println(x == y) // true
Strings in Go are compared by value, not reference. No .equals() needed. This catches out Java and Python developers.
Comparing Structs
Structs are comparable if all their fields are comparable types. Go compares them field by field:
type Point struct {
X, Y int
}
p1 := Point{1, 2}
p2 := Point{1, 2}
fmt.Println(p1 == p2) // true
Add a slice or map field, and the struct becomes non-comparable:
type Config struct {
Tags []string // slice field — struct is now non-comparable
}
== will not compile. Respect that.
Comparing Pointers
Pointer comparison with == checks whether both pointers point to the same memory address, not whether the values at those addresses are equal:
a := Point{1, 2}
b := Point{1, 2}
pa := &a
pb := &b
fmt.Println(pa == pb) // false — different addresses
fmt.Println(*pa == *pb) // true — same values
Dereference deliberately.
Comparing Interfaces
Two interface values are equal if they have the same dynamic type and equal dynamic values:
var i1 interface{} = 42
var i2 interface{} = 42
fmt.Println(i1 == i2) // true
var i3 interface{} = []int{1, 2}
var i4 interface{} = []int{1, 2}
// fmt.Println(i3 == i4) — runtime panic: comparing uncomparable type
That last example compiles but panics at runtime. This is a genuine danger: comparing interface{} (or any) values that might hold slices or maps will crash. Guard with type assertions first.
Deep Comparison with reflect.DeepEqual
When you need to compare slices, maps, or nested structs, reflect.DeepEqual is the standard-library option:
import "reflect"
a := []int{1, 2, 3}
b := []int{1, 2, 3}
fmt.Println(reflect.DeepEqual(a, b)) // true
m1 := map[string]int{"x": 1}
m2 := map[string]int{"x": 1}
fmt.Println(reflect.DeepEqual(m1, m2)) // true
Caveats:
- Slower than direct comparison — avoid in hot paths.
- Distinguishes between
nilslices and empty slices ([]int(nil)vs[]int{}), causing false negatives in tests. - Treats
NaN != NaNfor floats (IEEE 754 compliant, but often unexpected).
Use it in tests, not production logic where performance matters.
Ordering with the cmp Package and Generics
Since Go 1.21, the cmp package provides a clean Compare function for ordered types:
import "cmp"
fmt.Println(cmp.Compare(3, 5)) // -1
fmt.Println(cmp.Compare(5, 5)) // 0
fmt.Println(cmp.Compare(7, 5)) // 1
For sorting and ordering custom types, implement the cmp.Ordered constraint or provide a comparison function. This replaces a lot of the boilerplate that used to require manual sort.Interface implementations.
Writing a Custom Equal Method
For domain types with complex equality rules, define your own method:
type Money struct {
Amount int64
Currency string
}
func (m Money) Equal(other Money) bool {
return m.Amount == other.Amount && m.Currency == other.Currency
}
This pattern makes intent explicit, is testable, and doesn’t rely on field ordering or reflection.
Practical Rules
- Use
==for primitives, comparable structs, and pointers-by-address. - Use
reflect.DeepEqualin tests only; be aware of nil/empty slice gotchas. - Never compare interface values holding non-comparable types — guard with type assertions first.
- Define
.Equal()methods on your domain types. - Use
cmp.Comparefor ordered comparisons in generic or sorting contexts.
Go’s comparison model rewards deliberateness. Once you internalise these rules, you’ll write clearer, more predictable code and spend less time chasing equality bugs.