Question
I want to check whether two structs, slices, and maps are equal in Go.
However, I am running into problems with the following code:
package main
import (
"fmt"
"reflect"
)
type T struct {
X int
Y string
Z []int
M map[string]int
}
func main() {
t1 := T{
X: 1,
Y: "lei",
Z: []int{1, 2, 3},
M: map[string]int{
"a": 1,
"b": 2,
},
}
t2 := T{
X: 1,
Y: "lei",
Z: []int{1, 2, 3},
M: map[string]int{
"a": 1,
"b": 2,
},
}
fmt.Println(t2 == t1)
// error: invalid operation: t2 == t1 (struct containing []int cannot be compared)
fmt.Println(reflect.ValueOf(t2) == reflect.ValueOf(t1))
// false
fmt.Println(reflect.TypeOf(t2) == reflect.TypeOf(t1))
// true
a1 := []int{1, 2, 3, 4}
a2 := []int{1, 2, 3, 4}
fmt.Println(a1 == a2)
// invalid operation: a1 == a2 (slice can only be compared to nil)
m1 := map[string]int{
"a": 1,
"b": 2,
}
m2 := map[string]int{
"a": 1,
"b": 2,
}
fmt.Println(m1 == m2)
// invalid operation: m1 == m2 (map can only be compared to nil)
}
Why does this happen, and what is the correct way to compare two structs, slices, or maps for equality in Go?
Short Answer
By the end of this page, you will understand how equality works in Go, why some values can be compared with == and others cannot, and how to correctly compare structs, slices, and maps. You will also learn when to use reflect.DeepEqual and when writing your own comparison logic is a better choice.
Concept
In Go, not every type supports comparison with ==.
A value is comparable only if Go defines == for that type. This matters because Go treats equality as a language rule, not as something automatically inferred from the contents of a value.
What can be compared with ==
These types are comparable:
- booleans
- numbers
- strings
- pointers
- channels
- interfaces, if their dynamic values are comparable
- arrays, if their element type is comparable
- structs, if all fields are comparable
What cannot be compared with ==
These types are not comparable to each other with ==:
- slices
- maps
- functions
They can only be compared to nil.
Why your struct comparison fails
This struct:
type T struct {
X int
Y string
Z []int
M map[string]int
}
contains a slice field and a map field . Since slices and maps are not comparable, the struct itself is also not comparable with .
Mental Model
Think of Go values as falling into two groups:
- simple boxes: values Go knows how to compare directly with
== - containers with internal storage: values like slices and maps that point to underlying data
A slice is not the data itself. It is more like a small label that describes:
- where the data starts
- how long it is
- how much capacity it has
A map is also a runtime-managed structure, not just a plain value with obvious byte-for-byte equality.
So when you write ==, Go asks:
Is this type one of the kinds I know how to compare directly?
If yes, it compares. If no, the code does not compile.
For deep comparison, imagine opening both containers and checking their contents item by item. That is what reflect.DeepEqual does.
Syntax and Examples
Basic rules
// Comparable values
fmt.Println(10 == 10) // true
fmt.Println("go" == "go") // true
// Not allowed
// []int{1,2} == []int{1,2} // compile error
// map[string]int{"a":1} == map[string]int{"a":1} // compile error
Comparing structs with ==
A struct can be compared only if all of its fields are comparable.
package main
import "fmt"
type Point struct {
X int
Y int
}
func main() {
p1 := Point{X: 1, Y: 2}
p2 := Point{X: 1, Y: 2}
fmt.Println(p1 == p2) // true
}
This works because both X and Y are int, and int is comparable.
Step by Step Execution
Consider this example:
package main
import (
"fmt"
"reflect"
)
func main() {
a := []int{1, 2, 3}
b := []int{1, 2, 3}
fmt.Println(reflect.DeepEqual(a, b))
}
Here is what happens step by step:
a := []int{1, 2, 3}creates a slice containing three integers.b := []int{1, 2, 3}creates another slice with the same values.a == bwould not compile, because slices cannot be compared with==except againstnil.reflect.DeepEqual(a, b)inspects both values.- It sees that both values are slices.
- It checks whether they have the same length.
- It compares each element in order:
1vs12vs
Real World Use Cases
1. Comparing API responses in tests
When you call an API and decode JSON into Go structs, you often want to verify that the actual response matches the expected response.
if !reflect.DeepEqual(got, want) {
t.Errorf("unexpected response")
}
2. Detecting configuration changes
Suppose your application loads a config file into a struct. You may want to reload the app only if the new config is different from the old one.
3. Comparing cached data
A service may skip expensive work if a newly computed result is equal to the cached version.
4. Data processing pipelines
When processing records, you may compare maps or slices to check whether data has changed after a transformation step.
5. Validating input normalization
If your code sorts, cleans, or deduplicates input, you may compare the original and transformed results to determine whether a change occurred.
Real Codebase Usage
In real Go projects, developers often avoid relying on generic deep comparison everywhere.
Common patterns
Guard clauses
Return early when values are obviously different.
func sameLength(a, b []int) bool {
if len(a) != len(b) {
return false
}
return true
}
Manual comparison for important domain types
If only specific fields matter, compare those directly.
func sameUser(a, b User) bool {
return a.ID == b.ID && a.Email == b.Email
}
This avoids accidental comparison of fields that should not affect equality.
Validation before comparison
Sometimes developers normalize data first.
- trim spaces
- sort slices if order should not matter
- fill default values
Then compare the normalized values.
Error handling and testing
In tests, reflect.DeepEqual is common for quick assertions. In production logic, manual comparisons are often preferred because they are explicit.
Common Mistakes
Mistake 1: Using == on slices or maps
Broken code:
a1 := []int{1, 2, 3}
a2 := []int{1, 2, 3}
fmt.Println(a1 == a2)
Why it fails:
- slices cannot be compared with
==except tonil
Use instead:
fmt.Println(reflect.DeepEqual(a1, a2))
Mistake 2: Assuming a struct is always comparable
Broken code:
type T struct {
Numbers []int
}
fmt.Println(T{Numbers: []int{1}} == T{Numbers: []int{1}})
Why it fails:
- a struct is comparable only if all its fields are comparable
Mistake 3: Comparing reflect.Value instead of underlying data
Broken idea:
Comparisons
| Approach | Works for | Deep content check | Notes |
|---|---|---|---|
== | Comparable types only | No | Fast and simple, but not allowed for slices and maps |
reflect.DeepEqual | Structs, slices, maps, arrays, nested values | Yes | Convenient, especially in tests |
| Manual comparison function | Any type | Yes, if you implement it | Best when only some fields matter or custom rules apply |
== vs reflect.DeepEqual
| Feature | == |
|---|
Cheat Sheet
Quick rules
==works only on comparable types.- slices, maps, and functions are not comparable with
==. - slices and maps can only be compared to
nil. - a struct is comparable only if every field is comparable.
- an array is comparable if its element type is comparable.
- use
reflect.DeepEqual(a, b)for deep comparison of structs, slices, and maps.
Common examples
fmt.Println(1 == 1) // true
fmt.Println("a" == "a") // true
fmt.Println([2]int{1, 2} == [2]int{1, 2}) // true
// invalid
// []int{1,2} == []int{1,2}
// map[string]int{"a":1} == map[string]int{"a":1}
Deep equality
reflect.DeepEqual([]int{, }, []{, })
reflect.DeepEqual(
[]{: },
[]{: },
)
FAQ
Why can't slices be compared with == in Go?
Slices are not directly comparable in Go because they are descriptors for underlying data, not plain comparable values. You can only compare a slice to nil.
How do I compare two slices in Go?
A common general approach is reflect.DeepEqual(a, b). If you need custom rules, write your own comparison function.
How do I compare two maps in Go?
Use reflect.DeepEqual(m1, m2) for a deep content comparison, or loop through keys and values manually if you need custom behavior.
Can I compare two structs with ==?
Yes, but only if all fields inside the struct are comparable. If the struct contains a slice or map, == will not compile.
What is the difference between reflect.TypeOf and reflect.DeepEqual?
reflect.TypeOf checks the type metadata. reflect.DeepEqual checks whether two values contain equal data.
Is reflect.ValueOf(a) == reflect.ValueOf(b) a valid deep comparison?
No. That compares reflect.Value objects, not the underlying values deeply.
Mini Project
Description
Build a small Go program that compares two configuration objects and reports whether they are equal. This demonstrates how equality works for structs containing slices and maps, which cannot be compared directly with ==.
Goal
Create a program that compares nested config data correctly and prints whether the configs match.
Requirements
- Define a
Configstruct that contains simple fields, a slice field, and a map field. - Create two config values with the same contents.
- Show that
==cannot be used directly on the struct. - Use
reflect.DeepEqualto compare the configs. - Change one nested value and show that the comparison becomes false.
Keep learning
Related questions
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.
Blank Identifier Imports in Go: What `_` Means in an Import Statement
Learn what `_` means in a Go import, why blank identifier imports run package init code, and when to use them safely.
Calling Functions Across Files in the Same Go Package
Learn how Go uses packages across multiple files, why functions may appear undefined, and how to organize code correctly.