Skip to the code
cf.

Deep equal

Write deepEqual(a, b). It returns true when the two values have the same structure and contents, and false otherwise.

Primitives compare with Object.is, so NaN equals NaN but 0 and -0 differ.

Two objects are equal when they have the same prototype and the same own enumerable string keys, in any order, with deeply equal values. Arrays also need the same length. So an array never equals an object with the same keys, and an array with a hole doesn't equal one holding undefined there.

Compare dates by their time, and regular expressions by source and flags.

Two Maps are equal when they have the same size and every key of one is in the other (as has finds it) with a deeply equal value. Two Sets are equal when they have the same size and every member of one is in the other, as has finds it.

Values can contain cycles: give an answer without recursing forever.

deepEqual({ a: [1, { b: 2 }] }, { a: [1, { b: 2 }] }); // true
deepEqual({ a: 1, b: 2 }, { b: 2, a: 1 });             // true
deepEqual(new Map([["k", 1]]), new Map([["k", 2]]));   // false
deepEqual([1, 2], { 0: 1, 1: 2 });                     // false

What the tests check

  1. compares nested objects and arrays by contents
    expect(deepEqual({ a: [1, { b: 2 }] }, { a: [1, { b: 2 }] })).toBe(true);
    expect(deepEqual({ a: [1, { b: 2 }] }, { a: [1, { b: 3 }] })).toBe(false);
    expect(deepEqual([1, 2], [1, 2, 3])).toBe(false);
    
  2. ignores key order but not missing or extra keys
    expect(deepEqual({ a: 1, b: 2 }, { b: 2, a: 1 })).toBe(true);
    expect(deepEqual({ a: 1 }, { a: 1, b: 2 })).toBe(false);
    expect(deepEqual({ a: 1, b: 2 }, { a: 1 })).toBe(false);
    expect(deepEqual({ a: undefined }, { b: undefined })).toBe(false);
    expect(deepEqual({ a: undefined }, {})).toBe(false);
    
  3. compares primitives with Object.is
    expect(deepEqual(NaN, NaN)).toBe(true);
    expect(deepEqual(0, -0)).toBe(false);
    expect(deepEqual(1, "1")).toBe(false);
    expect(deepEqual(null, null)).toBe(true);
    expect(deepEqual(null, {})).toBe(false);
    expect(deepEqual({}, null)).toBe(false);
    expect(deepEqual(undefined, null)).toBe(false);
    
  4. tells arrays from objects, holes from undefined, and lengths apart
    expect(deepEqual([1, 2], { 0: 1, 1: 2 })).toBe(false);
    expect(deepEqual([], {})).toBe(false);
    expect(deepEqual([, 1], [undefined, 1])).toBe(false);
    expect(deepEqual([, 1], [, 1])).toBe(true);
    expect(deepEqual([], [,])).toBe(false);
    expect(deepEqual([1], [1, ,])).toBe(false);
    
  5. compares dates by time and regular expressions by source and flags
    expect(deepEqual(new Date(0), new Date(0))).toBe(true);
    expect(deepEqual(new Date(0), new Date(1))).toBe(false);
    expect(deepEqual(/a/g, /a/g)).toBe(true);
    expect(deepEqual(/a/g, /a/i)).toBe(false);
    expect(deepEqual(/a/, /b/)).toBe(false);
    
  6. compares Maps by key and deep value, and Sets by membership
    expect(deepEqual(new Map([["k", { x: 1 }]]), new Map([["k", { x: 1 }]]))).toBe(true);
    expect(deepEqual(new Map([["k", 1]]), new Map([["k", 2]]))).toBe(false);
    expect(deepEqual(new Map([["a", 1]]), new Map([["b", 1]]))).toBe(false);
    expect(deepEqual(new Set([1, 2]), new Set([2, 1]))).toBe(true);
    expect(deepEqual(new Set([1]), new Set([2]))).toBe(false);
    expect(deepEqual(new Set([1]), new Set([1, 2]))).toBe(false);
    
  7. handles cycles
    const a = { name: "x" };
    a.self = a;
    const b = { name: "x" };
    b.self = b;
    const c = { name: "y" };
    c.self = c;
    expect(deepEqual(a, b)).toBe(true);
    expect(deepEqual(a, c)).toBe(false);
    const list = [1];
    list.push(list);
    const other = [1];
    other.push(other);
    expect(deepEqual(list, other)).toBe(true);
    
  8. requires the same prototype
    class Point {
      constructor() {
        this.x = 1;
      }
    }
    expect(deepEqual(new Point(), { x: 1 })).toBe(false);
    expect(deepEqual(new Point(), new Point())).toBe(true);
    const bare = Object.create(null);
    bare.x = 1;
    expect(deepEqual(bare, { x: 1 })).toBe(false);
    

Learn more

solution.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).