Prompt
Implement deepEqual(a, b) — structural equality:
- Primitives compare by value;
NaNequalsNaN(unlike===) - Arrays: same length, elements deep-equal in order
- Plain objects: same key set (an explicit
undefinedvalue still counts as a key), values deep-equal nullequals onlynull— remembertypeof null === 'object'- No JSON.stringify — it lies about key order, undefined, and NaN
Solution
Interviewers ask for deepEqual because the naive answer — JSON.stringify(a) === JSON.stringify(b) — fails in exactly the ways that matter in production: key order changes the string, undefined values vanish, NaN becomes null, and a Date silently becomes a string. Naming those failures before writing code is the senior move.
What the reference solution encodes:
a === bfirst is both the primitive comparison and a reference-equality fast path — comparing an object to itself costs O(1), which matters when this runs in a memoized selector.- The null guard sits before
typeofbecausetypeof null === 'object'is a 30-year-old bug you're expected to know by name. - Key-count comparison +
hasOwnPropertycatches both extra keys and inherited keys. Usingb[key] !== undefinedas an existence check is the classic subtle bug — it conflates "missing" with "present but undefined". - Scope honestly: this doesn't handle
Date,Map/Set, RegExp, or cycles (a cyclic structure recurses forever — production versions carry aWeakMapof visited pairs). Saying "I'd add a WeakMap for cycles" unprompted is a strong signal.
The React connection: this is what React.memo's comparator could do but deliberately doesn't — deep comparison costs O(tree size) per render, which is why React bets on shallow equality plus immutable updates instead.
Say it: "I check reference equality first, handle NaN and null explicitly, then compare key sets recursively — and I'd flag that cycles need a WeakMap and that this cost is exactly why React chose shallow comparison."