What must be true for two Go interface values to be equal with `==`?
answer
- an interface value is a pair
- both halves must match
- same underlying type is not enough
- int(1) and int64(1) are different keys
- identical uncomparable dynamic types panic
basics
~20 sTwo interface values are equal only when both hold nothing at all, or when their dynamic types are identical and their dynamic values are equal. An any holding int(1) never equals an any holding int64(1).
solid answer
~50 sAn interface value is a pair: a dynamic type and a dynamic value. `==` compares both halves. Two interface values are equal when both are nil — no type and no value — or when the dynamic types are *identical* and the dynamic values compare equal under that type's own rules. "Identical" means the same named type, not merely the same underlying type, so `any(1) == any(int64(1))` is false and `any(Celsius(20)) == any(float64(20))` is false, even though both compile. When one operand is a concrete value it is converted to the interface type first, so `x == 1` on an `any` asks whether `x` holds an `int` with value 1. There is one run-time hazard: if both sides hold the *same* uncomparable dynamic type, such as `[]int`, the comparison panics rather than failing to compile.
code
go · 9 linestype Celsius float64
var a any = 1
var b any = int64(1)
var c any = Celsius(20)
var d any = float64(20)
// a == b is false: dynamic types int and int64 differ
// c == d is false: Celsius and float64 are distinct typesgo deeper
Recall that an interface value carries a type as well as a value, and that both must match for == to be true. Know that an any holding an int is not equal to an any holding an int64.
Explain the dynamic-type/dynamic-value pair, why identical underlying types are not enough, and what happens when one operand is concrete: it is converted to the interface type before comparing.
Demonstrate the consequences in real code: sentinel errors matching by pointer address, JSON numbers arriving as float64, and the run-time panic when both sides hold the same uncomparable dynamic type.
Own the API guidance that pushes these decisions back to compile time — concrete key and parameter types instead of any, one exported sentinel per condition, and a documented rule for whether a published type is safe to compare.
## An interface value is a pair At run time a Go interface value carries two things: the **dynamic type** (the concrete type currently stored) and the **dynamic value** (the data itself, or a pointer to it). The zero interface value carries neither, and that — not "a value that happens to be zero" — is what `== nil` tests. Equality follows straight from the pair: > Two interface values are equal if their dynamic types are identical and their dynamic values are equal, or if both are nil. Both halves matter, and the first half is the one that trips people. ## "Identical type", not "same-looking value" ```go var a any = 1 // dynamic type int var b any = int64(1) // dynamic type int64 fmt.Println(a == b) // false ``` This compiles because the static types on both sides are `any`, which is comparable. It is false because `int` and `int64` are different types. Go performs no numeric conversion inside `==` on interfaces; the ordinary rule that you cannot mix `int` and `int64` without a conversion still applies, it is just hidden behind the interface. The same holds for defined types over the same underlying type: ```go type Celsius float64 var x any = Celsius(20) var y any = float64(20) // x == y is false: Celsius and float64 are distinct types ``` And for pointer versus value: ```go type P struct{ N int } var u any = P{1} var v any = &P{1} // u == v is false: P and *P are different dynamic types ``` ## Comparing an interface with a concrete value When only one side is an interface, the concrete operand must be assignable to the interface type, and it is converted before comparison. So for `var x any`: - `x == 1` asks whether `x` holds an `int` with value 1 (the untyped constant 1 defaults to `int`). - `x == "ok"` asks whether `x` holds a `string` equal to `"ok"`. This is why sentinel comparison works: `err == io.EOF` is an interface-to-interface comparison where both sides must hold the same dynamic type and value. ## Why two errors with the same text are not equal `errors.New` returns a `*errorString` — a **pointer**. Interface equality therefore compares the pointer's address: ```go e1 := errors.New("not found") e2 := errors.New("not found") // e1 == e2 is false: two distinct allocations ``` Sentinel errors work precisely because everyone compares against the *same package-level variable*, so the address matches. Two independently constructed errors with identical text are different values, and no amount of string similarity changes that. ## The equality of the second half Once the dynamic types match, the values are compared using that type's ordinary comparison rules: numerically for numbers, byte-wise for strings, address-wise for pointers and channels, field by field for structs, element by element for arrays. So two `any` values both holding `Point{1, 2}` **are** equal, because struct equality is structural. ## The run-time hazard Interface types are comparable *statically* — the compiler always accepts `x == y` on two interface values. But if the dynamic types are identical and that type is **not** comparable — a slice, a map, or a function value — the comparison cannot be carried out and the runtime panics, with a message naming the uncomparable type. This is the one place in Go where `==` fails at run time rather than at compile time, and it is a genuine trap for APIs that pass `any` around. If you must compare values whose dynamic type you do not control, do not use `==` directly: type-switch to the shapes you support, or use `reflect.DeepEqual`, which handles slices and maps by walking them instead of panicking. ## Practical rules - Never rely on numeric equality across an interface boundary; normalise to one concrete type before storing. - When you define a sentinel value to be compared with `==`, export it as a single package-level variable and say so in the doc comment. - Prefer a concrete static type when you can: `map[string]int` and `func(a, b Point) bool` push these questions back to compile time, where they belong. - Remember that decoding formats such as JSON into `any` produces `float64` for every number, so a value that "was" an int on the way in will not compare equal to an `int` on the way out.
- Why are two errors returned by `errors.New` with identical text not equal under `==`?`errors.New` returns a pointer to an unexported struct, so the interface's dynamic value is an address. Two calls allocate two distinct values, so the addresses differ and `==` is false. Sentinel comparison works only because callers compare against the one exported package-level variable, whose address is shared.
- You decode JSON into an `any` and compare it with `== 3`. Why does it fail?`encoding/json` decodes every JSON number into a `float64` when the target is `any`, so the dynamic type is `float64` while the untyped constant 3 becomes an `int`. The dynamic types differ, so the comparison is false. Compare against `3.0`, or decode into a typed struct field instead.
- Two `any` values hold pointers to two distinct but field-identical structs. Are they equal?No. The dynamic type `*T` is identical on both sides, so the comparison proceeds, but the dynamic value of a pointer is its address and the two allocations have different addresses. Dereference and compare the structs, or store the struct values in the interfaces rather than pointers.
It is like matching a parcel by both its label and its contents: same contents under a different label is still a different parcel.
saying these in an interview costs you the question
- Says two interfaces holding 20 are equal regardless of type
- Thinks == on interfaces compares only the dynamic values
- Believes Go converts int64 to int inside the comparison
- Assumes comparing two interface values can never panic
- Compares two errors.New results and expects true