Question

How to Compare Two Slices for Equality in Go

gocomparisonslice

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 nil and 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 0 matches: 1 == 1
  • element 1 matches: 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 false on first mismatch
  • return true if 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 nil vs 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.

Take Quiz

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 equal
  • for i := range a visits 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

  • s1 contains 4, 5, 6
  • s2 contains 4, 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) is 3
  • len(b) is 3
  • lengths match, so execution continues

4. Loop starts at index 0

if a[0] != b[0]
  • a[0] is 4
  • b[0] is 4
  • they match

5. Loop continues at index 1

  • a[1] is 5
  • b[1] is 5
  • they match

6. Loop continues at index 2

  • a[2] is 6
  • b[2] is 6
  • 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 0 matches
  • index 1 does not match (5 != 9)
  • function returns false immediately

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.

Take Quiz

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.

  • nil slice: 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

ApproachBest forProsCons
Manual loopProduction code, simple typed slicesFast, explicit, easy to controlMore code than one function call
reflect.DeepEqualTests, quick checks, generic comparisonsConvenient, works for many typesUses reflection, less explicit
Custom business-rule comparisonSpecial equality rulesFlexibleYou must define and maintain the rules

nil vs empty slice

ValueExampleLengthUsually means
nil slicevar s []int0No slice allocated or no value set
Empty slice[]int{}0A real slice with zero elements

These often behave similarly, but they are not always the same conceptually.

Arrays vs slices in Go

FeatureArraySlice
Fixed sizeYesNo
Can use ==Yes, if element type is comparableNo, only with nil
Common in everyday Go codeLess commonVery common
RepresentsActual data collectionView into underlying array

This difference helps explain why arrays can be compared directly but slices cannot.

Take Quiz

Cheat Sheet

Quick rules

  • Slices cannot be compared with == or !=
  • A slice can only be compared to nil
  • To check equality by contents:
    1. compare lengths
    2. compare each element in order
  • reflect.DeepEqual can 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
  • nil slice 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.

Take Quiz

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 []int values and returns a bool
  • Return false if 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
Take Quiz

Keep learning

Related questions

View more