skip to content

Why can reflect.DeepEqual(x, x) return false for some values of x?

level: middleimportance: should knowfreq 38%

answer

  1. reflexivity is not guaranteed here
  2. the recursion bottoms out in ==
  3. one numeric value equals nothing
  4. only nil ones are deeply equal
  5. a callback field poisons the comparison

basics

~20 s

reflect.DeepEqual is not reflexive. A float NaN anywhere in the value fails the == that DeepEqual falls back to for numbers, and a non-nil func value is deeply equal to nothing at all, itself included.

solid answer

~40 s

`reflect.DeepEqual` bottoms out in `==` for numbers, strings, bools and channels, and IEEE-754 says a NaN is not equal to anything, including the same NaN. So a struct holding `math.NaN()` compared against a copy of itself reports false. The second cause is func values: the documented rule is that two func values are deeply equal only if **both are nil**, so any struct carrying a non-nil callback — a comparator, a hook, a lazily installed handler — can never be deeply equal to another struct, not even a copy. Both cases produce the same confusing symptom: a comparison that fails while every printed field looks identical. Recognise them by their shape rather than by the diff, because a diff of the printed values shows nothing wrong.

code

go · 10 lines
go
type sample struct {
	Value  float64
	Render func() string
}

s := sample{Value: 1.5, Render: func() string { return "1.5" }}
fmt.Println(reflect.DeepEqual(s, s)) // false: a non-nil func is never deeply equal

n := sample{Value: math.NaN()}
fmt.Println(reflect.DeepEqual(n, n)) // false: NaN is not equal to itself

go deeper

for a junior

Recall the two exceptions by name: a NaN float and a non-nil function value. Knowing that reflect.DeepEqual can say false for a value compared with itself is most of the answer at this level.

for a middle

Explain the mechanism: the recursion ends in == for numbers, IEEE-754 makes NaN unequal to everything, and the documented func rule says only nil funcs are deeply equal. Be able to say why funcs have no equality in Go.

for a senior

Show how you would spot this in a failing comparison where every printed field looks identical, and how you would restructure the type — data separated from hooks, or an Equal method — instead of special-casing each site.

for a principal

Own the type-design consequence: a struct that mixes data with behaviour has no usable equality, which quietly rules out caching, deduplication and change detection built on comparing values.

## Deep equality is not an equivalence relation It is tempting to assume `reflect.DeepEqual(x, x)` is always true — reflexivity is the first thing you expect from anything called equality. Go's version does not guarantee it, for two independent reasons that both come from the leaves of the recursion. ### Cause one: NaN `reflect.DeepEqual` recurses through composite kinds and, when it reaches a number, a string, a bool or a channel, decides with Go's `==`. Floating-point values obey IEEE-754, which defines NaN — the result of `0.0/0.0`, `math.Sqrt(-1)` and friends — as unequal to every value, itself included. `math.NaN() == math.NaN()` is false, and so is `f == f` when `f` is NaN. So a struct with a `float64` field holding NaN is not deeply equal to a bit-identical copy. The bits are the same; the comparison operator says no anyway. There is no tolerance knob and no NaN option in `reflect.DeepEqual` — it has no configuration surface at all. This matters most for numeric data with missing or undefined measurements, where NaN is used as the "no reading" marker. A recorded sample set that contains a single NaN will never compare equal to itself once it has been through a copy, a decode, or any operation that produces a fresh value. ### Cause two: func values The documented rule is blunt: **func values are deeply equal if both are nil; otherwise they are not deeply equal.** Not "if they point at the same code", not "if they are the same closure" — simply never, unless both are nil. The reason is that Go has no meaningful equality for functions. You cannot write `f == g` for two func values either; the language allows only comparison against `nil`. Two closures can share the same code pointer while capturing completely different variables, so comparing code addresses would report equality for things that behave differently, and comparing captured state would require walking an environment the language does not expose. `reflect.DeepEqual` declines rather than guessing. The practical consequence is that any struct which carries behaviour — a validator function, a formatter, an on-change hook, an injected clock as `func() time.Time` — becomes uncomparable by deep equality as soon as that field is set. The failure is silent and total: the comparison returns false without saying which field caused it. ## The confusing asymmetry What makes this hard to diagnose is that whether the reflexive case fails depends on how the value is packaged. If the NaN or the func sits directly in a struct or array that you pass by value, `reflect.DeepEqual` has no shortcut available and must compare field by field, so it reaches the `==` (false for NaN) or the func rule (false for non-nil funcs). If the same data sits behind a pointer, in a slice or in a map, and you compare the container with itself, identity short-circuits can fire before the elements are ever examined — two identical pointers are deeply equal by `==`, and two slice values sharing a backing array and length are deeply equal without element comparison. So the same NaN can produce true or false depending on whether the comparison had an identity shortcut to take. That is not a bug; it is what "a recursive relaxation of `==`" means. But it does mean you cannot reason about the result from the printed data alone. ## Adjacent leaf kinds, for contrast - **Channels** compare with `==`, and a channel value equals itself. A struct holding the same channel in both operands is fine; two separately made channels of the same type are not equal. - **Pointers** are deeply equal when `==` says so, or when the pointed-to values are deeply equal. - **Interfaces** are deeply equal when both are nil, or when they hold deeply equal concrete values — which means an interface field holding a func drags the func rule in with it. - **Strings and bools** are unremarkable: `==` decides, and it is reflexive. ## Working around it There is no hook in `reflect.DeepEqual`, so the options are all outside it. For NaN, compare explicitly: treat two values as equal when both satisfy `math.IsNaN`, otherwise use `==` (or a tolerance if the domain calls for one). A hand-written `Equal` method on the type is the durable version of this, because it puts the domain's definition of equality where the type is defined. For funcs, exclude the field. Either compare a struct that omits behaviour — separate the data from the hooks — or write an `Equal` method that compares only the data fields. A type whose identity genuinely includes a callback does not have a useful equality in Go, and designing around that is better than fighting it. The interview-sized summary: `reflect.DeepEqual` is not reflexive, NaN and non-nil funcs are why, and neither has a knob.

  • Why does the func rule refuse to compare two non-nil functions at all?
    Because Go has no equality for functions — `f == g` does not compile, only `f == nil` does. Two closures can share one code pointer while capturing different variables, so comparing addresses would call unequal things equal. `reflect.DeepEqual` declines rather than picking a misleading definition.
  • Does a channel field cause the same problem?
    No. Channels are compared with `==`, which is reflexive, so a struct holding the same channel value on both sides is deeply equal. Two separately created channels of the same type are not equal, but that is ordinary identity, not the func rule.
  • How would you compare structures that legitimately contain NaN?
    Write the comparison yourself: treat both-NaN as equal via `math.IsNaN` on each side, and use `==` or a tolerance otherwise. Putting that in an `Equal` method on the type keeps the domain's rule with the type. `reflect.DeepEqual` offers no option to change its behaviour.

saying these in an interview costs you the question

  • Says DeepEqual is always reflexive
  • Thinks two identical closures are deeply equal
  • Assumes NaN equals NaN because the bits match
  • Believes DeepEqual has a float tolerance setting
  • Claims a struct with a func field cannot be compared at all