When are two Go interface values equal with ==, and when does that comparison panic?
answer
- both halves must match
- int and int64 are not the same type
- the compiler cannot see inside an interface
- slices, maps and funcs have no ==
- the check moves to runtime and panics
basics
~20 sTwo interface values are equal only when their dynamic types are identical and their values compare equal for that type. Different dynamic types are never equal. If the shared dynamic type is not comparable, such as a slice or map, the comparison panics at runtime.
solid answer
~50 sInterface equality compares both halves. The dynamic types must be identical - an `any` holding `int(1)` is never equal to one holding `int64(1)` - and then the values are compared using that type's own equality. Two empty interfaces are equal, and comparing any interface to `nil` is always safe. The sharp edge is that Go decides comparability at compile time for concrete types but only at runtime for interfaces: if both sides turn out to hold a slice, map, function, or a struct containing one, the comparison panics with `comparing uncomparable type`. That is why an `any` or an `error` used as a map key, or compared with `==`, can blow up on a value shape you never tested. If a type might hold one of those, compare something explicit instead of the interface itself.
code
go · 7 linesvar a any = 1
var b any = int64(1)
fmt.Println(a == b) // false: different dynamic types
var s1 any = []int{1}
var s2 any = []int{1}
fmt.Println(s1 == s2) // panics: comparing uncomparable type []intgo deeper
Know the headline: two interface values are equal only when they hold the same type and the same value. An int and an int64 with the same number are not equal.
Explain why comparability becomes a runtime property once a value is inside an interface, and name the types that trigger the panic: slices, maps, functions, and structs containing them.
Show where this reaches production - a map keyed on any, a set of unknown values, an error type carrying a slice field - and give the alternative you would ship instead of the direct comparison.
Own the type-level decision: whether an interface your packages expose is documented as holding comparable implementations only, and what that constraint costs the teams implementing it.
### The rule An interface value is a pair of a dynamic type and a value. Two interface values compare equal with `==` when: 1. their dynamic types are **identical**, and 2. the stored values are equal according to that type's equality. Both conditions, in that order. Two interface values holding different types are unequal without even looking at the values: ```go var a any = 1 var b any = int64(1) fmt.Println(a == b) // false: int and int64 are different dynamic types ``` This surprises people who expect numeric coercion. Interfaces do not coerce - the type half is part of the identity. Two interface values that are both empty are equal, which is exactly the `x == nil` case, and it is always safe. ### Mixed operands When one operand is an interface and the other is a concrete value, the concrete one is converted to the interface type first, and then the same rule applies. So `err == ErrNotFound` compares dynamic types and values; it is true only when the error holds the very same sentinel value. ### The runtime panic Go's compiler normally refuses to compile `==` on a type that is not comparable - slices, maps and functions. But when the values are hidden behind an interface, the compiler cannot know what will be inside, so it emits the comparison and the **runtime** checks: ```go var s1 any = []int{1} var s2 any = []int{1} fmt.Println(s1 == s2) // panics: comparing uncomparable type []int ``` The panic message names the offending dynamic type. It fires when the two dynamic types are identical and that type is uncomparable; if the types differ, the answer is already false and nothing panics. Comparing against `nil` never panics, because the nil side has no type to compare. The same trap hides in structs: a struct type is comparable only if all its fields are, so a struct with one `[]string` field is uncomparable and panics the same way once boxed in an interface. ### Where this bites in practice - **Map keys.** A `map[any]V` or `map[string]any` compared for equality relies on the same machinery. Inserting a key whose dynamic type is a slice panics at insert time. - **Error comparison.** Comparing errors with `==` is fine for sentinel values of comparable types, but a custom error type whose struct carries a slice field will panic if two of them are ever compared directly. - **Deduplication.** A set built as `map[any]struct{}` over values of unknown shape is a landmine; use a key derived from the value instead. - **Test assertions.** Hand-rolled equality over `any` values behaves differently from a deep-equality helper such as `reflect.DeepEqual`, which handles slices and maps without panicking. ### Choosing the safe alternative When the dynamic type is genuinely unknown and might be uncomparable, do not compare the interface. Options: - constrain the interface so only comparable implementations exist, and say so in the doc comment; - compare an explicit key extracted from the value, such as an identifier field or a canonical string form; - use `reflect.DeepEqual` when structural equality is what you actually want, accepting its cost and its different semantics for nil versus empty slices. ### Nilness and equality together The two ideas reinforce each other. `x == nil` is just the equality rule applied to the empty pair: it is true only when the left operand has no type and no value. An interface holding a nil pointer has a type, so it is unequal to nil - and it **is** equal to another interface holding a nil pointer of the same type, since both halves match. That last fact is occasionally useful: two typed nils of the same type do compare equal to each other. ### What an interviewer is listening for The phrase both halves, the concrete example of `int` versus `int64` being unequal, and the awareness that comparability is a runtime property once a value is inside an interface. Candidates who have been bitten usually mention the map-key case unprompted.
- Are two interface values that both hold a nil pointer of the same type equal to each other?Yes. Both halves match: the dynamic types are identical and the stored pointers are both nil, so the comparison is true. Neither of them is equal to nil, though, because nil for an interface means the empty pair. This is a neat way to show that equality and nilness follow the same one rule about both halves.
- Why can the compiler reject == on a slice but not on an interface that might hold one?Comparability is a property of a concrete type, and for a slice variable the compiler sees that type and rejects the code. For an interface variable the dynamic type is only known at runtime, so the compiler emits the comparison and the runtime checks comparability, panicking with `comparing uncomparable type` if the shared dynamic type has no equality.
- What is a safe alternative when values of unknown shape must be deduplicated?Do not key a map on the interface value directly. Extract a key you control - an identifier field, or a canonical encoded form of the value - and key on that comparable type. Where structural equality is genuinely what you want, `reflect.DeepEqual` handles slices and maps without panicking, at the cost of reflection and slightly different nil-versus-empty semantics.
saying these in an interview costs you the question
- Says two interfaces holding 1 and int64(1) are equal
- Believes the compiler always rejects uncomparable comparisons
- Thinks comparing an interface to nil can panic
- Uses map[any]V for values of unknown shape without concern
- Claims interface equality compares only the stored value