Question
In Go, how can you check whether two slices are equal when the == and != operators cannot be used?
For example, this code does not compile:
package main
import "fmt"
func main() {
s1 := []int{1, 2}
s2 := []int{1, 2}
fmt.Println(s1 == s2)
}
The compiler reports:
invalid operation: s1 == s2 (slice can only be compared to nil)
What is the correct way to compare two slices in Go?
Short Answer
By the end of this page, you will understand why slices cannot be compared directly with == in Go, how slice equality really works, and the common ways to compare slices safely. You will also learn when to use a manual loop, when reflect.DeepEqual is appropriate, and how real Go codebases usually handle slice comparison.
Concept
A slice in Go is a small descriptor that points to an underlying array, along with a length and capacity. Because of that, a slice is not a simple value like an int or bool.
Go does not allow direct comparison of two slices using == or !=, except comparing a slice to nil.
var s []int
fmt.Println(s == nil) // valid
But this is not allowed:
s1 := []int{1, 2}
s2 := []int{1, 2}
// fmt.Println(s1 == s2) // invalid
Why Go disallows slice-to-slice comparison
If Go allowed == on slices, it would need to decide what equality means:
- Are two slices equal if they point to the same underlying array?
- Or if they contain the same values?
- Should capacity matter?
- Should
niland empty slices be treated the same?
Instead of guessing, Go requires you to choose the comparison logic explicitly.
What slice equality usually means
In most programs, two slices are considered equal if:
- they have the same length, and
- each element at the same index is equal
For []int{1, 2} and []int{1, 2}:
- length is the same:
2 - element
0matches:1 == 1 - element
1matches:2 == 2
So they are equal by contents, even though == cannot be used.
Common ways to compare slices
1. Manual comparison
This is the most direct and common approach:
- check lengths
- loop through elements
- return
falseon first mismatch - return
trueif all elements match
2. reflect.DeepEqual
Go's reflect package provides reflect.DeepEqual, which can compare many composite values, including slices.
reflect.DeepEqual(s1, s2)
This is convenient, but many Go developers avoid it in performance-sensitive or strongly typed code because:
- it is more general than needed
- it uses reflection
- it may have behavior you should understand carefully, especially with
nilvs empty slices
3. Package helpers
In modern Go, some standard-library packages provide equality helpers for specific types, such as byte slices. In some cases, helper functions are preferred because they are clearer and faster than reflection.
The main lesson is: slice comparison in Go is usually content comparison, done explicitly.
Mental Model
Think of a slice like a window looking into an array.
The slice itself is not the data. It is more like a note saying:
- where the data starts
- how many items are visible
- how much space is available
Two different windows can show the same numbers, but they are still different slice values internally.
So asking s1 == s2 is like asking whether two windows are the same object. Go does not allow that comparison.
What you usually care about is: do both windows show the same sequence of values?
That is why content-based comparison is the right mental model for slice equality.
Syntax and Examples
Manual comparison function
For beginner-friendly and efficient code, write a helper function:
package main
import "fmt"
func slicesEqual(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func main() {
s1 := []int{1, 2}
s2 := []int{1, 2}
s3 := []int{1, 3}
fmt.Println(slicesEqual(s1, s2)) // true
fmt.Println(slicesEqual(s1, s3)) // false
}
How it works
len(a) != len(b)means they cannot be equalfor i := range avisits every index- if any pair of values differs, return
false - if the loop finishes, all elements matched
Using reflect.DeepEqual
For quick checks, especially in tests or small scripts:
package main
import (
"fmt"
"reflect"
)
func main() {
s1 := []int{1, 2}
s2 := []int{1, 2}
fmt.Println(reflect.DeepEqual(s1, s2)) // true
}
When this is useful
- quick prototypes
- tests
- comparing nested or mixed structures
When to be careful
- reflection can be slower
- behavior may differ from your intended business rules
- explicit comparison is often clearer in production code
Step by Step Execution
Consider this code:
package main
import "fmt"
func slicesEqual(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func main() {
s1 := []int{4, 5, 6}
s2 := []int{4, 5, 6}
fmt.Println(slicesEqual(s1, s2))
}
Step-by-step
1. main creates two slices
s1contains4, 5, 6s2contains4, 5, 6
They are separate slices, but their contents are the same.
2. slicesEqual(s1, s2) is called
Now a refers to s1 and b refers to s2.
3. Lengths are checked
if len(a) != len(b) {
return false
}
len(a)is3len(b)is3- lengths match, so execution continues
4. Loop starts at index 0
if a[0] != b[0]
a[0]is4b[0]is4- they match
5. Loop continues at index 1
a[1]is5b[1]is5- they match
6. Loop continues at index 2
a[2]is6b[2]is6- they match
7. Loop finishes
No mismatch was found, so:
return true
8. fmt.Println prints the result
true
Example with a mismatch
If s2 were []int{4, 9, 6}:
- index
0matches - index
1does not match (5 != 9) - function returns
falseimmediately
This early return is efficient because it stops as soon as equality is impossible.
Real World Use Cases
Slice comparison appears often in Go programs.
1. Validating API results
You may call an API and compare returned IDs to expected IDs:
expected := []int{101, 102, 103}
received := []int{101, 102, 103}
A content comparison confirms the response is correct.
2. Checking processed data
After transforming input data, you may compare the output to an expected slice in tests.
Examples:
- sorted numbers
- filtered records
- mapped values
3. Comparing user permissions
A system may store allowed roles or feature flags in slices. Comparing contents helps verify whether a user has the exact expected set.
4. Detecting changes in configuration
A service might reload configuration and compare old and new lists such as:
- enabled hosts
- allowed IPs
- active modules
If the slices differ, the app may reload resources.
5. Unit testing
This is one of the most common use cases. Developers often compare expected and actual slices in tests to verify function behavior.
Real Codebase Usage
In real Go projects, developers usually avoid writing raw slice comparison logic everywhere. Instead, they use a small helper function or a well-chosen package function.
Common patterns
Guard clause for length
if len(a) != len(b) {
return false
}
This is a classic guard clause: fail early if equality is impossible.
Early return on mismatch
for i := range a {
if a[i] != b[i] {
return false
}
}
This avoids unnecessary work.
Reusable helper function
In production code, equality rules are often placed in a helper:
func equalIDs(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
This keeps business logic clean.
Validation before processing
A codebase might compare slices only after checking inputs are not malformed.
Tests often use helper libraries or reflection
In tests, readability often matters more than micro-optimizations. Developers may use:
reflect.DeepEqual- testing helpers
- custom assertion functions
Why explicit comparison is preferred
- easier to understand
- type-specific and safe
- usually faster than reflection
- makes equality rules obvious
Common Mistakes
1. Trying to use == directly on slices
Broken code:
s1 := []int{1, 2}
s2 := []int{1, 2}
fmt.Println(s1 == s2) // compile error
Fix
Compare lengths and elements instead.
2. Comparing only lengths
Broken code:
func slicesEqual(a, b []int) bool {
return len(a) == len(b)
}
This is wrong because slices with the same length can still contain different values.
Example:
[]int{1, 2}
[]int{9, 9}
Both have length 2, but they are not equal.
3. Forgetting order matters
[]int{1, 2, 3}
[]int{3, 2, 1}
A normal slice comparison treats these as different because element positions differ.
If your use case ignores order, you need different logic such as sorting first or counting values.
4. Using reflect.DeepEqual without understanding nil vs empty
var a []int = nil
b := []int{}
These may be treated differently depending on your comparison method and business rules.
nilslice: no slice value- empty slice: slice with zero elements
Sometimes they should be considered equal in your application, and sometimes not.
5. Writing duplicate comparison code everywhere
If you compare slices in many places, create a helper function. Repeated comparison code increases bugs and inconsistency.
6. Ignoring nested or custom element types
If a slice contains structs or other complex values, each element may need its own comparison rules. A simple != may not work for every element type.
Comparisons
Slice comparison approaches
| Approach | Best for | Pros | Cons |
|---|---|---|---|
| Manual loop | Production code, simple typed slices | Fast, explicit, easy to control | More code than one function call |
reflect.DeepEqual | Tests, quick checks, generic comparisons | Convenient, works for many types | Uses reflection, less explicit |
| Custom business-rule comparison | Special equality rules | Flexible | You must define and maintain the rules |
nil vs empty slice
| Value | Example | Length | Usually means |
|---|---|---|---|
nil slice | var s []int | 0 | No slice allocated or no value set |
| Empty slice | []int{} | 0 | A real slice with zero elements |
These often behave similarly, but they are not always the same conceptually.
Arrays vs slices in Go
| Feature | Array | Slice |
|---|---|---|
| Fixed size | Yes | No |
Can use == | Yes, if element type is comparable | No, only with nil |
| Common in everyday Go code | Less common | Very common |
| Represents | Actual data collection | View into underlying array |
This difference helps explain why arrays can be compared directly but slices cannot.
Cheat Sheet
Quick rules
- Slices cannot be compared with
==or!= - A slice can only be compared to
nil - To check equality by contents:
- compare lengths
- compare each element in order
reflect.DeepEqualcan compare slices, but manual comparison is often clearer
Basic pattern
func slicesEqual(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
nil comparison
var s []int
fmt.Println(s == nil) // true
Reflection option
reflect.DeepEqual(a, b)
Important edge cases
- same length does not mean same contents
- order matters
nilslice and empty slice may need special handling depending on your app- arrays and slices are not compared the same way in Go
FAQ
Why can't slices be compared with == in Go?
Because slices are descriptor types, not simple comparable values. Go only allows slice comparison with nil.
What is the easiest way to compare two slices in Go?
For simple typed slices, write a helper that compares length and then each element.
Can I use reflect.DeepEqual to compare slices?
Yes. It works for many cases and is especially common in tests, but explicit comparison is often clearer in production code.
Are two empty slices equal in Go?
If both have zero length and your comparison checks only contents, they may be treated as equal. But nil and empty slices are not always identical concepts.
Does slice order matter when comparing equality?
Yes. []int{1, 2} and []int{2, 1} are different if you compare by index.
Can arrays be compared with == in Go?
Yes, arrays can be compared directly if their element type is comparable.
What should I use in tests to compare slices?
A helper function or reflect.DeepEqual are both common choices, depending on how explicit you want the test to be.
Mini Project
Description
Build a small Go program that compares two slices of integers entered in code and reports whether they are equal. This project reinforces the idea that slices must be compared by contents, not with ==.
Goal
Create a reusable slice comparison function and use it to check multiple pairs of slices.
Requirements
- Write a function that accepts two
[]intvalues and returns abool - Return
falseif the slice lengths are different - Compare elements in order and stop early on the first mismatch
- Print results for at least three example slice pairs
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.