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 Z and a map field M. Since slices and maps are not comparable, the struct itself is also not comparable with ==.
Why reflect.ValueOf(t2) == reflect.ValueOf(t1) is false
reflect.ValueOf returns reflect.Value objects. Comparing those with == compares the reflect.Value wrappers, not the original data deeply. That is not the same as checking whether the underlying structs contain equal data.
Why reflect.TypeOf(t2) == reflect.TypeOf(t1) is true
reflect.TypeOf returns the type metadata. Both values have the same type T, so the comparison is true.
The usual solution: reflect.DeepEqual
When you want to compare the contents of structs, slices, maps, arrays, and other nested values, the common standard-library tool is:
reflect.DeepEqual(a, b)
It checks whether two values are deeply equal by recursively comparing their contents.
Example:
fmt.Println(reflect.DeepEqual(t1, t2)) // true
fmt.Println(reflect.DeepEqual(a1, a2)) // true
fmt.Println(reflect.DeepEqual(m1, m2)) // true
Why this matters in real programs
Equality checks are used everywhere:
- testing expected vs actual results
- comparing configuration objects
- detecting changes in data
- validating API responses
- deciding whether to update cached state
Knowing whether == checks value equality, identity, or is not allowed at all helps you avoid compiler errors and subtle bugs.
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.
Comparing structs, slices, and maps with reflect.DeepEqual
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},
}
a1 := []int{1, 2, 3, 4}
a2 := []int{1, 2, 3, 4}
m1 := map[string]int{"a": 1, "b": 2}
m2 := map[string]int{"a": 1, "b": 2}
fmt.Println(reflect.DeepEqual(t1, t2)) // true
fmt.Println(reflect.DeepEqual(a1, a2)) // true
fmt.Println(reflect.DeepEqual(m1, m2)) // true
}
Writing your own comparison logic
Sometimes you should compare fields manually instead of using reflection.
package main
import "fmt"
type User struct {
ID int
Name string
}
func equalUser(a, b User) bool {
return a.ID == b.ID && a.Name == b.Name
}
func main() {
u1 := User{ID: 1, Name: "Ana"}
u2 := User{ID: 1, Name: "Ana"}
fmt.Println(equalUser(u1, u2)) // true
}
This is often clearer, faster, and safer when you know exactly which fields matter.
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:
1vs12vs23vs3
- Since all elements match, the result is
true. fmt.Println(...)printstrue.
Now compare that with this example:
fmt.Println(reflect.ValueOf(a) == reflect.ValueOf(b))
That does not do a deep content comparison. It compares two reflect.Value objects, which is a different thing entirely. So it is not the right tool for checking whether two slices contain the same data.
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.
Comparing slices or maps by business rules
For example:
- order may matter for a slice in one feature
- order may not matter in another feature
- some map keys may be ignored
In those cases, write custom comparison functions instead of assuming generic deep equality matches your business rules.
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:
reflect.ValueOf(t1) == reflect.ValueOf(t2)
Why it is wrong:
- this compares
reflect.Valuewrappers, not the contents deeply
Use:
reflect.DeepEqual(t1, t2)
Mistake 4: Forgetting that nil and empty values can differ
Example:
var a []int = nil
b := []int{}
fmt.Println(reflect.DeepEqual(a, b)) // false
Why this surprises beginners:
- both may look “empty”, but one is
niland the other is an allocated empty slice
Mistake 5: Using deep equality when business equality is different
Example:
// These may be considered equal by your app logic
// if order does not matter, but DeepEqual will say false.
a := []int{1, 2, 3}
b := []int{3, 2, 1}
Avoid this by defining equality based on your actual requirements.
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 | == | reflect.DeepEqual |
|---|---|---|
| Compiler support | Built into the language | Function from reflect |
| Works on slices | No | Yes |
| Works on maps | No | Yes |
| Works on structs with slice/map fields | No | Yes |
| Compares type only | No | No |
| Compares nested contents | No | Yes |
reflect.TypeOf vs reflect.ValueOf vs reflect.DeepEqual
| Function | What it gives you | Good for equality? |
|---|---|---|
reflect.TypeOf(x) | The type of x | Only checks whether types match |
reflect.ValueOf(x) | A reflection wrapper around the value | Not for deep equality by itself |
reflect.DeepEqual(a, b) | A deep comparison result | Yes |
When to choose each
- Use
==for simple comparable values. - Use
reflect.DeepEqualfor quick deep checks, especially in tests. - Use manual comparison when the definition of “equal” depends on business rules.
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{1, 2}, []int{1, 2}) // true
reflect.DeepEqual(
map[string]int{"a": 1},
map[string]int{"a": 1},
) // true
Important edge case
var a []int = nil
b := []int{}
reflect.DeepEqual(a, b) // false
Practical advice
- Prefer
==for simple types. - Prefer manual comparison for domain-specific rules.
- Use
reflect.DeepEqualwhen you need generic deep comparison.
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.
Is nil slice equal to an empty slice in Go?
Not with reflect.DeepEqual. A nil slice and an empty non-nil slice are different values.
Should I always use reflect.DeepEqual?
No. It is useful, especially in tests, but manual comparison is often better in production code when equality should follow business rules.
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
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
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.