How to Compare Values, Structs, Slices, and Maps in Go
To perform a golang compare operation between two values, use the double equals operator (==) for basic scalar types, pointers, channels, and structs composed entirely of comparable fields. For reference types like slices and maps, direct operator comparison causes a compile-time error, so you must use the slices.Equal or maps.Equal functions introduced in the Go standard library, or package-level reflection utilities like google/go-cmp for complex deep equality checks.
Choosing the incorrect comparison strategy in Go can introduce subtle runtime panics, silent bugs caused by hidden pointer mismatches, or unnecessary CPU performance penalties. Because Go handles memory allocation and layout strictness differently than dynamic languages like JavaScript or Python, understanding how type memory representations impact value matching is essential for writing reliable backend software.
Which Go types support the direct comparison operator?
The standard equality operators (== and !=) check whether two values share identical underlying representations in memory, but Go limits their application strictly to comparable types. Booleans, numeric types (integers, floats, complex numbers), strings, pointers, channels, interfaces, and arrays of comparable types can all be evaluated directly using standard operators.
Structs can also be evaluated using ==, provided every field within the struct definition belongs to a comparable type. If two struct instances have matching field values across all fields, the comparison evaluates to true.
However, the Go specification explicitly classifies slices, maps, and functions as non-comparable types. The only valid comparison you can make using == on a slice, map, or function is checking whether it equals nil. Attempting to execute code such as sliceA == sliceB fails at compile time with an uncomparable type error.
How do you golang compare slices and maps?
To evaluate equality for slices and maps, you must iterate over elements or rely on standard library packages specifically designed for reference structures. Prior to Go 1.21, developers had to manually write loops or use byte comparison helpers. In modern Go versions, standard packages simplify element-by-element evaluations.
For one-dimensional sequences, the standard library slices.Equal function checks whether two slices have identical length and that elements at matching indices satisfy standard equality. If you are handling raw byte sequences specifically, such as cryptographic hashes or network buffer payloads, bytes.Equal offers optimized lower-level assembly routines that outperform generic slice loops.
For key-value pairs, maps.Equal verifies that both maps share the exact same key count and that values for identical keys evaluate to equal under the standard == rules. If you need custom matching logic—such as comparing custom objects contained inside a slice or checking float thresholds—you can pass custom comparator functions into slices.EqualFunc or maps.EqualFunc.
When should you use reflect.DeepEqual versus google/go-cmp?
When working with nested data structures, dynamic interface types, or JSON payload representations, top-level element comparison is insufficient. In these scenarios, you need recursive evaluation toolsets, primarily reflect.DeepEqual from the standard library or the external open-source package google/go-cmp.
The standard library reflect.DeepEqual traverses complex hierarchies recursively. It compares pointers by evaluating the underlying pointed-to values, unrolls struct fields regardless of visibility, and evaluates slice lengths alongside their elements. However, reflect.DeepEqual has major drawbacks: it is relatively slow, allocates heap memory during runtime inspection, treats nil slices and empty non-nil slices as unequal, and cannot be tuned to ignore specific struct fields like timestamps or generated IDs.
I recommend using google/go-cmp for unit tests and complex application logic instead of standard reflection. The cmp.Equal function allows you to define explicit options, such as ignoring unexported struct fields, allowing custom equivalence definitions, or setting numeric floating-point tolerance margins using cmpopts.EquateApprox. Additionally, when a comparison fails, cmp.Diff produces readable visual diffs showing exactly which nested struct key or slice index caused the mismatch.
How do you perform ordered comparisons in Go 1.21 and later?
Beyond checking basic equality, ordering comparisons (determining if value A is less than or greater than value B) require distinct considerations. For built-in ordered types—such as strings, integers, and floating-point numbers—the relational operators (<, <=, >, >=) operate directly at compile time.

Go 1.21 introduced the generic cmp standard library package, which standardizes relational comparison logic across generic algorithms. The cmp.Compare function accepts two arguments of any ordered type and returns an integer indicating their relationship:
- -1 if the first value is less than the second value
- 0 if both values are equal
- +1 if the first value is greater than the second value
This standardized three-way comparison logic simplifies building custom sorting algorithms, binary search structures, and priority queues, removing the need to write custom boilerplate comparison loops across different primitive types.
What are the performance differences across Go comparison methods?
The execution speed and memory impact of comparison methods vary significantly depending on whether evaluation occurs via direct machine instructions or runtime type reflection. Selecting the wrong method inside high-frequency loops or hot HTTP request paths can introduce throughput bottlenecks.
| Comparison Method | Supported Types | Performance Profile | Primary Use Case |
|---|---|---|---|
== Operator |
Scalars, Arrays, Comparable Structs | Instant (Inlined Assembly) | Direct primitive & ID matching |
bytes.Equal |
Byte Slices ([]byte) |
Very Fast (SIMD / Optimized) | Buffer & Cryptographic check |
slices.Equal / maps.Equal |
Slices and Maps | Fast (Linear O(N) Scan) | Standard collection equality |
reflect.DeepEqual |
Any Go Type (Recursive) | Slow (Runtime Allocations) | Quick prototyping, legacy code |
cmp.Equal (go-cmp) |
Any Go Type (Configurable) | Moderate (Dynamic Reflection) | Unit tests & diff assertion |
Direct relational operators execute in a single CPU instruction, requiring zero heap allocations. Standard helpers like slices.Equal perform linear iterations over elements without dynamic interface boxing, keeping memory allocation clean. Conversely, reflect.DeepEqual incurs continuous memory allocations as it inspects dynamic type headers, making it hundreds of times slower than direct or standard collection comparisons.
What edge cases break value comparison in Go?
When working with complex data models, several subtle behaviors in Go can lead to unexpected equality evaluations or runtime panics. Awareness of these edge cases prevents frustrating bugs.

First, floating-point IEEE 754 standards dictate that math.NaN() == math.NaN() always evaluates to false. If a struct or slice contains float values set to NaN, direct operator checks and standard library equality helpers will return false, even if both structures hold identical NaN values. If your application handles mathematical computations where NaN must equal NaN, you must use cmpopts.EquateNaNs() from package google/go-cmp.
Second, struct comparisons can panic at runtime if a struct contains an interface field holding an uncomparable underlying type. While the struct compiles cleanly, evaluating structA == structB at runtime causes a panic if the interface field contains a slice or map. When handling dynamic interfaces inside structs, stick to explicit structural checks or package-level diff helpers.
Third, understand the difference between nil and empty collections. A nil slice (declaring var s []int) and an empty initialized slice (s := []int{}) both have a length and capacity of zero. However, standard reflection checks like reflect.DeepEqual will mark them as unequal because their internal memory pointer headers differ. Standard helpers like slices.Equal correctly treat both as equal since they only inspect element contents.
Who should avoid advanced dynamic comparison libraries?
While packages like google/go-cmp are powerful, they are not designed for every situation. You should avoid importing dynamic reflection comparison tools inside production API runtime paths that handle thousands of requests per second. The allocation overhead introduced by dynamic field inspection adds latency and increases garbage collection pressure.
Instead, write dedicated, strongly typed Equal() receiver methods on your domain models for runtime application code. Reserving heavy reflection tools for unit testing suites gives you clean test feedback without sacrificing real-time production throughput.
Related guides
Frequently asked questions
Can you compare two structs directly using the double equals operator in Go?
Yes, you can compare two structs using the == operator as long as all fields within the struct are comparable types. If any field is a slice, map, function, or interface containing a non-comparable type, evaluating the struct with == will result in a compile error or runtime panic.
What is the difference between reflect.DeepEqual and google/go-cmp?
Standard library reflect.DeepEqual offers built-in recursive comparison but cannot be customized, treats nil vs empty collections as unequal, and lacks clear diff reporting. In contrast, google/go-cmp provides configurable comparison options, custom transformer rules, and human-readable visual diff outputs suitable for unit testing.
How do you compare slices without using third-party packages in modern Go?
You can compare slices in modern Go using the standard library’s slices.Equal function for standard types, or slices.EqualFunc for custom element matching logic. These helpers perform element-by-element checks without requiring third-party library imports.