Compare Structs, Slices, and Maps in Go (Equality and Deep Equality)

Learn how to compare two structs, slices, and maps in Go: struct equality with ==, slice and map checks with slices.Equal and maps.Equal (Go 1.21+), reflect.DeepEqual, test-focused go-cmp, and when to write your own logic.

Published

Updated

Read time 6 min read

Reviewed byDeepak Prasad

Go code example comparing struct, slice, and map equality with reflect.DeepEqual

Comparing int, string, or bool values in Go is straightforward with == and !=. Readers often need to compare two structs for struct equality, compare two slices where index order matters (the usual golang slice compare case), or compare maps by keys and values without caring about iteration order. This guide walks through the options that match real codebases: the == operator where the language allows it, reflect.DeepEqual, the standard slices and maps helpers (Go 1.21 onward), github.com/google/go-cmp/cmp for tests, and hand-written comparisons when you need explicit rules.

Tested with Go 1.24 on Linux; slices and maps examples require Go 1.21 or newer.


Compare structs, slices, and maps in Go

Struct equality with ==

If you need Go compare structs behavior for a plain data struct whose fields are all comparable, == is often the clearest choice: it is fast, predictable, and typed. A struct is comparable only if every field type is comparable (for example no []T, map[K]V, or func fields, and no nested non-comparable structs).

Run the example: the first pair matches on every field; the second differs on Age.

go
package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	p1 := Person{"Bob", 20}
	p2 := Person{"Bob", 20}
	p3 := Person{"Bob", 40}
	fmt.Println("p1 == p2:", p1 == p2)
	fmt.Println("p1 == p3:", p1 == p3)
}
Output

You should see p1 == p2: true and p1 == p3: false.

Compare two slices: slices.Equal and reflect.DeepEqual

For golang compare slices or go compare two slices style checks, prefer slices.Equal when elements are comparable and order should match. It returns true when lengths match and each index compares equal with ==.

For byte slices, bytes.Equal is equally idiomatic.

Run the example: both slices.Equal calls should print true; changing order or an element would yield false.

go
package main

import (
	"fmt"
	"slices"
)

func main() {
	a := []int{1, 2, 3}
	b := []int{1, 2, 3}
	c := []int{1, 3, 2}
	fmt.Println("slices.Equal(a, b):", slices.Equal(a, b))
	fmt.Println("slices.Equal(a, c):", slices.Equal(a, c))
}
Output

reflect.DeepEqual also compares slice elements, but remember it treats a nil slice and a non-nil empty slice as not deeply equal (for example []int(nil) vs []int{}), which is a common source of bugs if you expected them to match. Use DeepEqual when you already depend on reflect and accept those semantics, or for quick tests.

Compare two maps: maps.Equal and reflect.DeepEqual

For golang compare maps, maps.Equal compares the sets of keys and the value at each key using == on the values. Key order does not matter (maps are unordered); equality is about the same keys mapping to deeply equal values per the function’s definition.

Run the example: the duplicate-key maps compare equal; changing one email makes maps.Equal false.

go
package main

import (
	"fmt"
	"maps"
)

func main() {
	m1 := map[string]string{"Anna": "a@x.test", "Bob": "b@x.test"}
	m2 := map[string]string{"Bob": "b@x.test", "Anna": "a@x.test"}
	m3 := map[string]string{"Anna": "a@x.test", "Bob": "b2@x.test"}
	fmt.Println("maps.Equal(m1, m2):", maps.Equal(m1, m2))
	fmt.Println("maps.Equal(m1, m3):", maps.Equal(m1, m3))
}
Output

One reflect.DeepEqual example for structs, slices, and maps

reflect.DeepEqual walks values recursively. It is widely used in tests to compare golang compare two structs shapes with nested fields, slices, and maps in one call. Read the package docs for exact rules (nil vs empty slices, time.Time, cycles, and so on).

Run the example: struct and slice pairs print true where values match; the map line shows false when a value differs.

go
package main

import (
	"fmt"
	"reflect"
)

type person struct {
	name string
	age  int
}

func main() {
	p1 := person{"Bob", 20}
	p2 := person{"Bob", 20}
	p3 := person{"Anna", 20}
	fmt.Println("struct:", reflect.DeepEqual(p1, p2), reflect.DeepEqual(p1, p3))

	s1 := []int{1, 2, 3}
	s2 := []int{1, 2, 3}
	s3 := []int{1, 2}
	fmt.Println("slice:", reflect.DeepEqual(s1, s2), reflect.DeepEqual(s1, s3))

	map1 := map[string]string{"Anna": "a@x.test", "Bob": "b@x.test"}
	map2 := map[string]string{"Anna": "a@x.test", "Bob": "b2@x.test"}
	fmt.Println("map:", reflect.DeepEqual(map1, map2))
}
Output

go-cmp for tests: cmp.Equal and ignoring fields

For golang compare two structs in unit tests, cmp.Equal from github.com/google/go-cmp/cmp is often clearer than reflect.DeepEqual because you can attach options (for example ignore timestamps or specific fields with cmpopts.IgnoreFields). The module is intended for tests: it may panic on unsupported comparisons and is not optimized as a general-purpose production equality API.

This snippet uses modules outside the standard library; run it locally with a go.mod that requires github.com/google/go-cmp.

go
package main

import (
	"fmt"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
)

type Person struct {
	Name string
	Age  int
}

func main() {
	p1 := Person{"Bob", 20}
	p2 := Person{"Bob", 20}
	p3 := Person{"Bob", 40}
	fmt.Println("cmp p1,p2:", cmp.Equal(p1, p2))
	fmt.Println("cmp p1,p3:", cmp.Equal(p1, p3))
	fmt.Println("cmp p1,p3 ignore Age:", cmp.Equal(p1, p3, cmpopts.IgnoreFields(Person{}, "Age")))
}

If you run it locally with go run ., you should see true, false, and true for those three lines.

To print struct values while debugging comparisons, see printing a struct in Go.

Custom comparison logic

When business rules are stricter than == or slices.Equal (for example case-insensitive strings, tolerance for floats, or ignoring certain map keys), write a small function that returns false as soon as a mismatch is found. The pattern below compares a struct that holds a slice and a map using length checks and explicit loops; it only uses the standard library so you can run it as-is.

go
package main

import "fmt"

type T struct {
	Z []int
	M map[string]string
}

func compare(a, b T) bool {
	if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) {
		return false
	}
	for i, v := range a.Z {
		if b.Z[i] != v {
			return false
		}
	}
	for k, v := range a.M {
		if b.M[k] != v {
			return false
		}
	}
	return true
}

func main() {
	t1 := T{[]int{1, 2, 3}, map[string]string{"Anna": "a@x.test", "Bob": "b@x.test"}}
	t2 := T{[]int{1, 2, 3}, map[string]string{"Bob": "b@x.test", "Anna": "a@x.test"}}
	fmt.Println(compare(t1, t2))
}
Output

You should see true because keys and values align even though map literal order differs.


Summary

In Go, struct equality with == is the default when the struct type is comparable. For slice compare scenarios where order matters, use slices.Equal (or bytes.Equal for bytes) on Go 1.21+, and for map compare use maps.Equal so key/value pairs are compared without relying on iteration order. reflect.DeepEqual remains a single-call option for nested test data, provided you accept its nil-slice and other edge-case semantics. For richer test assertions and field filtering, use go-cmp. When product rules do not match any built-in definition, a short custom function keeps behavior obvious to the next reader.


References


Frequently Asked Questions

1. When can I compare two structs in Go with ==?

You can use == and != when the struct type is comparable: every field must be a comparable type (no slices, maps, or functions, and no structs containing those). Two structs are equal if every field is equal. Nested structs must also be fully comparable for the outer struct to use ==.

2. How do I compare two slices in Go?

For element-by-element equality of same-length slices, use slices.Equal from the standard library slices package (Go 1.21+). For []byte, bytes.Equal is fine. reflect.DeepEqual also compares slice elements but treats nil and empty non-nil slices as different, which surprises many people.

3. How do I compare two maps in Go for the same keys and values?

Use maps.Equal from the standard library maps package (Go 1.21+): it compares key sets and values regardless of iteration order. reflect.DeepEqual works on maps too but shares the same nil-map and edge-case semantics as elsewhere.

4. Is reflect.DeepEqual appropriate for production business logic?

It is convenient but easy to misuse: it treats nil and empty slices differently, does not use time.Time’s Equal method, and can surprise you with unexported fields and cycles. Prefer explicit ==, slices.Equal, maps.Equal, or small custom functions for production invariants; reserve DeepEqual mainly for tests or quick diagnostics unless you understand the semantics.

5. What is golang struct equality vs deep equality?

Struct equality with == means every comparable field matches per Go’s rules. Deep equality (reflect.DeepEqual or go-cmp) walks nested values with extra rules, including unexported fields for DeepEqual in some cases, which is why it is popular in tests but riskier in production code paths.
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise …