Comparing Slices in Go: How to Avoid Losing Performance and Your Sanity
\n\nImagine this: you're writing a high-load service in Go, and you need to compare two byte or string slices. It seems simple, right? But as always, the devil is in the details. On Stack Overflow, the question "Checking the equality of two slices" has gathered thousands of views — and for good reason. In this article, we'll break down the pitfalls that await a developer, how to write a correct comparison, and most importantly, how to do it as fast as possible.
\n\nWe won't use boring academic definitions. Instead, we'll use live examples, benchmarks, and practical tips you can apply today. Let's go!
\n\nWhy is a Simple Comparison Not Always Correct?
\n\nIn Go, a slice is not an array. It's a descriptor that contains a pointer to the underlying array, length, and capacity. Therefore, the == operator does not work for slices. Trying to write slice1 == slice2 will result in a compilation error:
invalid operation: slice1 == slice2 (slice can only be compared to nil)\n\nThis is not a bug, but a feature. Go developers intentionally prohibited direct slice comparison because it can lead to non-obvious consequences. For example, two slices can reference the same underlying array but have different lengths. Or vice versa — different arrays but identical data.
\n\nSo what to do? There are several approaches, and we'll look at each one.
\n\nMethod 1: Manual Loop (Basic but Reliable)
\n\nThe most obvious way is to write a function that iterates through all elements and compares them one by one. This gives you full control over the process.
\n\nfunc EqualSlices(a, b []int) bool {\n if len(a) != len(b) {\n return false\n }\n for i := range a {\n if a[i] != b[i] {\n return false\n }\n }\n return true\n}\n\nPros: transparency, no dependencies, ability to customize (e.g., comparison with tolerance for floats).
\nCons: you need to write it for each data type.
Method 2: reflect.DeepEqual (Universal but Slow)
\n\nThe standard library has a function reflect.DeepEqual that can compare any data structures — slices, maps, structs. It looks like this:
import "reflect"\n\nfunc main() {\n a := []int{1, 2, 3}\n b := []int{1, 2, 3}\n fmt.Println(reflect.DeepEqual(a, b)) // true\n}\n\nSeems perfect, right? But there's a catch: reflect.DeepEqual uses reflection, which is always expensive. On small slices, the difference is negligible, but if you have thousands of elements — performance will drop significantly.
Important: reflect.DeepEqual also considers nil and an empty slice as different! This can be surprising:
var a []int = nil\nb := []int{}\nfmt.Println(reflect.DeepEqual(a, b)) // false!\n\nMethod 3: bytes.Equal for Byte Slices (Optimal)
\n\nIf you're working with []byte, the best choice is the bytes.Equal function from the standard library. It's written in assembly and is highly optimized:
import "bytes"\n\nfunc main() {\n a := []byte("hello")\n b := []byte("hello")\n fmt.Println(bytes.Equal(a, b)) // true\n}\n\nThis is the fastest way for byte slices. Internally, it uses memcmp at the C level, providing a huge performance boost.
Method 4: Comparison Using crypto/subtle (For Cryptography)
\n\nThe crypto/subtle package deserves a special mention. Its ConstantTimeCompare function is designed for comparing secret data (passwords, tokens) without time leakage. A regular comparison can be vulnerable to timing attacks:
import "crypto/subtle"\n\nfunc