Implement deepEqual() for Structural Comparison

deepEqual checks if two values have the same structure and values, not the same reference. Handle primitives, arrays, plain objects, null, NaN, and the +0/-0 edge case.

11 min read
JavaScript
Interview
Implementation
Objects

TABLE OF CONTENTS

deepEqual(a, b) checks structural equality — same shape, same values, not the same reference. The interviewer tests whether you handle NaN, +0/-0, null vs {}, and arrays vs objects correctly.

Related deep-dive: Data #1 — Deep Clone & Deep Compare


What is deepEqual()?

deepEqual(a, b) performs structural comparison — it checks whether two values have the same shape and contents, regardless of whether they're the same object in memory. Unlike === (which compares references for objects), deepEqual recursively descends into nested structures and compares every leaf value.

This is the comparison counterpart to deepClone: one creates independent copies, the other verifies they're semantically identical. Both walk the same tree with the same recursion pattern.

The JavaScript-specific edge cases make this more nuanced than it looks:

  • NaNNaN === NaN is false (the only value not equal to itself). You need a special check: two NaN values are structurally equal.
  • +0 and -0+0 === -0 is true, but 1 / +0 !== 1 / -0 (Infinity vs -Infinity). Most implementations treat them as equal.
  • typeof null === "object" — comparing null to {} must return false, not throw from trying to recurse into null.
  • Arrays vs objects[] and {} have the same typeof, but one should compare by index and the other by key set.

Real-world use cases:

  • Should component re-render? — compare previous and next props/state deeply
  • Test assertionsexpect(result).toEqual(expected) in Jest is deep equality
  • Change detection — diff two config objects or API responses to find what changed
  • Caching — check if a cached result is still valid by comparing the input to the original

The interview tests recursion fundamentals plus two or three "did you know about this JavaScript quirk?" moments. Handling NaN, null, and array-vs-object branching cleanly is what separates a passing answer from a thorough one.


The Problem

"Implement deepEqual(a, b) that returns true if a and b have the same structure and values, and false otherwise. It should work for primitives, arrays, and plain objects."


Thought Process

The algorithm has ordered checks:

  1. Reference equality — if a === b, return true (fast exit). But watch: NaN !== NaN and +0 === -0.
  2. Type mismatch — if typeof a !== typeof b, return false.
  3. NaN handlingNaN is the only value where x !== x. If both are NaN, they're equal.
  4. null check — if either is null, they must be reference-equal (already handled by step 1).
  5. Primitive — return a === b.
  6. Array — compare lengths, then recurse element-wise.
  7. Object — compare key counts, then recurse value-wise.

Step 1 — Primitives with NaN

Loading editor...


Step 2 — Arrays

Loading editor...


Step 3 — Objects

Loading editor...


Step 4 — Edge Cases

NaN: deepEqual(NaN, NaN) must return true. Handled by the isNaN check.

+0 vs -0: Object.is(+0, -0) returns false, but +0 === -0 returns true. Our a === b early return treats them as equal. If the interviewer wants Object.is semantics, use Object.is instead of ===.

null vs {}: typeof null === 'object' and typeof {} === 'object'. Our null check before the typeof branch handles this.

Arrays vs objects: deepEqual([], {}) should return false. The Array.isArray(a) !== Array.isArray(b) check handles this.

Prototype properties: Object.keys only returns own enumerable properties, so inherited properties are ignored — which is correct for structural equality.


Full Solution

Loading editor...


What Interviewers Are Testing

  • NaN equality — knowing that NaN !== NaN and handling it explicitly
  • null vs object — knowing that typeof null === 'object' and guarding for it
  • Array vs object distinction — using Array.isArray before falling into the object branch
  • Short-circuit on reference equality — the fastest check, done first

Complexity

TimeSpace
deepEqualO(N) — each value visited onceO(D) — call stack, D = max depth

Interview Tips

  • Handle NaN first — "Since NaN !== NaN, I'll check if both values are NaN before anything else." This immediately tells the interviewer you know the quirk.
  • Check types before recursing — if types differ, return false immediately. Saves work and avoids comparing an array to an object.
  • State whether you want === or Object.is semantics for +0/-0 — most interviewers are fine with ===, but asking shows awareness.
  • Mention hasOwnProperty — checking it prevents inherited properties from affecting equality.

Related Questions


Let's Connect

© 2026 Naveen Karthik // Built with React & MUI