Implement deepEqual(a, b) — a function that returns true when two values look the same all the way down, regardless of whether they share the same reference. Primitives compare by value, arrays and plain objects compare by walking their structure recursively. Read MDN on Object.is and SameValueZero before you start — the question hinges on which equality flavor you pick for primitives.
function deepEqual(a: unknown, b: unknown): boolean;
// true if a and b are structurally equal across nested objects, arrays,
// primitives, and Dates. false otherwise.
deepEqual(1, 1); // true
deepEqual(NaN, NaN); // true — SameValueZero, not ===
deepEqual(0, -0); // true — SameValueZero again
deepEqual(null, undefined); // false — only null equals null
deepEqual({ a: 1, b: 2 }, { b: 2, a: 1 }); // true — key order doesn't matter
deepEqual([1, 2, 3], [3, 2, 1]); // false — array order does matter
deepEqual({ a: [1, { b: 2 }] }, { a: [1, { b: 2 }] }); // true — nested
deepEqual([1, 2], { 0: 1, 1: 2, length: 2 }); // false — array vs object
// Cycles must not blow the stack. Both inputs point at themselves; treat
// them as equal because the structural shape converges.
const a = {}; a.self = a;
const b = {}; b.self = b;
deepEqual(a, b); // true
// Dates compare by their numeric timestamp.
deepEqual(new Date(0), new Date(0)); // true
deepEqual(new Date(0), new Date(1)); // false
NaN === NaN is true, and 0 === -0 is true. This matches how Array.prototype.includes and Map/Set key lookup behave, and it's what most "deep equal" libraries (lodash.isEqual, Jest's toEqual) ship.a === b (or Object.is(a, b)), return true immediately. This both speeds up the common case and handles cycle entry, where both branches eventually arrive at the same pair you've already seen.{ a: 1, b: 2 } and { b: 2, a: 1 } are equal. Iterate one side's own enumerable keys and look each up on the other side; check key-set sizes first to catch the "extra key on one side" case.[1, 2] and [2, 1] are NOT equal. Arrays are positional; compare element-by-element at each index.false, Date vs plain object is false even if the object happens to expose the same .getTime(). Check Array.isArray on both sides; check instanceof Date on both sides.a.self = a) must not infinite-recurse. Use a WeakMap to remember <a, b> pairs already in flight; if you revisit one, assume equal (the recursion will terminate at the next non-shared field).Map, Set, RegExp, typed arrays, Symbol-keyed properties, or class instances with custom equality. Document these as limitations; the "Going further" section covers them.Unlock the solution & editor
Runnable editor + tests
Solve in the browser with instant Jest feedback.
Detailed solutions
Walkthroughs, edge cases, and complexity notes.
Multi-framework variants
React, Vue, Vanilla, Angular — same question, different stacks.