Which Go types can be compared with `==`, and which ones fail to compile?
answer
- the compiler decides, not the values
- three kinds are left out
- slice, map, func: only against nil
- structs and arrays inherit it from their parts
- same rule decides valid map keys
basics
~20 sBooleans, numbers, strings, pointers, channels, interfaces, and structs or arrays built entirely from comparable parts support ==. Slices, maps and functions do not: the compiler rejects ==, and the only equality they allow is a comparison against nil.
solid answer
~40 sGo's spec makes booleans, numeric types, strings, pointers, channels and interface types comparable, plus structs and arrays whose parts are all comparable. Slices, maps and function values are not comparable: writing `a == b` on them is a compile-time error, and the single exception is comparing them with `nil`. Comparability is a property of the *type*, not of the values, so it is settled by the compiler — a struct with one `[]byte` field can never be compared, whatever that field happens to hold. When you need element-wise equality for the three uncomparable kinds you call a function instead: `slices.Equal`, `maps.Equal`, `bytes.Equal`, or `reflect.DeepEqual` as a slow general fallback. The same rule decides map keys, which is why `map[[]string]int` does not compile while `map[[2]string]int` does.
code
go · 8 linestype Point struct{ X, Y int }
type Path struct{ Pts []Point }
func demo(a, b Point, p, q Path) {
_ = a == b // ok: every field of Point is comparable
_ = p == q // compile error: Path has a slice field
_ = p.Pts == nil // ok: a slice may only be compared with nil
}go deeper
Be ready to list the comparable kinds and name the three that are not: slices, maps and functions. Remember that those three may still be compared with nil.
Explain that comparability is a compile-time property of the type and is inherited recursively by structs and arrays, and say what == actually compares for pointers, strings and channels.
Show the production consequence: an API that takes any or exposes a struct as a map key inherits the rule, and adding one slice field to a shared struct breaks every == and every map key downstream.
Own the guidance that a type meant to be a key or a set element keeps all of its fields comparable, and that element-wise equality belongs in an explicit Equal helper rather than being smuggled into ==
## The rule Go defines equality on types, not on values. The language spec lists exactly which types are **comparable** — meaning `==` and `!=` are legal on them: - **Boolean** values. - **Numeric** values (integers, floats, complex), compared by numeric value. - **String** values, compared byte by byte. - **Pointer** values, equal when they hold the same address (or are both nil). - **Channel** values, equal when they came from the same `make` call (or are both nil). - **Interface** values, equal when their dynamic types are identical and their dynamic values are equal. - **Struct** types, comparable if *every* field type is comparable. - **Array** types, comparable if the element type is comparable; two arrays are equal when all corresponding elements are equal. Three kinds are deliberately left out: - **Slices** - **Maps** - **Function values** For those, `x == y` is a **compile-time error**, and the only equality expression allowed is `x == nil`. ## Why the three are excluded Each of the excluded kinds has no obvious, cheap definition of equality. Should two slices be equal when they share a backing array, or when their elements match? The first is surprising and the second is O(n) — and Go's `==` is meant to be a cheap, constant-shaped operation you can also use to hash a map key. Maps have the same problem plus unordered contents. Function values have no meaningful identity at all once the compiler is free to merge, inline or wrap them; a closure has captured state that nothing can inspect. Rather than pick a debatable answer, Go removes the operator and makes you say what you meant. Because the rule is about types, it is checked once at compile time and it is **transitive**. A struct is comparable only if all of its fields are, applied recursively: ```go type Inner struct{ A int } // comparable type Outer struct{ I Inner } // comparable type Broken struct{ M map[string]int } // NOT comparable type AlsoBroken struct{ B Broken } // NOT comparable, by inheritance ``` No runtime state can rescue `Broken`: even if the map field is nil, `Broken{} == Broken{}` does not compile. ## What comparison actually compares Beginners are often surprised by *which* thing is compared: - Two **pointers** are equal when they hold the same address. Two pointers to two distinct variables that happen to hold identical structs are **not** equal; `*p == *q` compares the pointed-to values instead. - Two **strings** are compared by content, not by data pointer, so `s == "ok"` is content equality. - Two **arrays** are compared element by element and copy by value, which is why `[16]byte` is a fine map key while `[]byte` is not. - Two **structs** are compared field by field, including unexported fields you cannot see from another package. - Two **interface** values compare the dynamic type first, then the dynamic value. ## What to do instead for the uncomparable three ```go slices.Equal(a, b) // element-wise for []T where T is comparable maps.Equal(m1, m2) // same keys, equal values bytes.Equal(p, q) // the fast path for []byte reflect.DeepEqual(x, y) // general, reflective, slow — a last resort ``` `reflect.DeepEqual` is not what `==` does under the hood; it is a separate reflective walk with its own rules (it treats a nil slice and an empty non-nil slice as different, for instance). Reach for the typed helpers first, keep `reflect.DeepEqual` for tests and unusual shapes. Function values have no equality helper at all — if you need to know "is this the same handler I registered?", store an identifier alongside it rather than trying to compare the function. ## Why this matters beyond `==` The comparable/not-comparable split is the same rule that decides what may be a **map key** and what may be an element of a set-shaped map. `map[K]V` requires `K` to be comparable, so the compiler rejects `map[[]string]int` with `invalid map key type`. It is also the rule behind interface equality panics: an interface type is statically comparable, but if the dynamic type it holds turns out to be a slice, map or function, the comparison fails at run time instead. That is the one place where the compile-time guarantee does not hold, and it is worth knowing before you build an API whose key type is `any`. ## Quick self-check Given `type S struct{ Name string; Tags []string }` — can you use `S` as a map key? No: the `Tags` field is a slice, so `S` is not comparable, and no value of `S` ever will be.
- If slices are not comparable, how do you check whether two `[]string` values hold the same elements?Call `slices.Equal(a, b)`, which walks both slices and compares element by element; for `[]byte` use `bytes.Equal`, which is faster. `reflect.DeepEqual` also works but is reflective and slow, and it has its own rules — it reports a nil slice and an empty non-nil slice as different. Plain `==` is only ever legal against `nil`.
- Are two Go pointers equal when they point at distinct variables holding the same value?No. Pointer equality compares addresses, so `p == q` is true only when both point at the same variable, or both are nil. To compare the pointed-to values you dereference: `*p == *q`, which is then subject to the ordinary comparability rules for that type.
- What does `==` on two Go channel values compare?Channel identity. Two channel values are equal when they were produced by the same `make` call, or when both are nil. It says nothing about buffered contents, capacity or direction, so `==` on channels answers "is this the same channel?", never "do these carry the same data?".
Comparability is like whether a shape has a barcode printed on it: the printing is decided when the type is designed, not when the box is filled.
saying these in an interview costs you the question
- Says == on two slices compares their elements
- Thinks reflect.DeepEqual is what == does under the hood
- Believes comparability depends on the values, not on the type
- Claims a struct with a map field compares fine when the map is nil
- Uses == on []byte instead of bytes.Equal