Skip to main content

DeepEqual Function

Data Comparison5 functions

Utility Overview

Details

Performs deep equality comparison between values, handling nested structures, various data types, and circular references

Category:Data Comparison
Functions:deepEqual, deepEqualWithCircularCheck, shallowEqual, deepEqualStrict, deepEqualJSON
Performance:

- Time: O(n) where n is total properties/elements across all structures - Space: O(d) where d is maximum nesting depth (recursion stack)

Concepts:
recursiontype checkingobject comparisonreference tracking

Implementation

deepEqual

Time: O(n) | Space: O(d)
export function deepEqual(a: unknown, b: unknown): boolean {
  // Strict equality check (handles primitives, null, undefined, same reference)
  if (a === b) return true;
  
  // Both must be objects from this point
  if (a == null || b == null) return false;
  if (typeof a !== 'object' || typeof b !== 'object') return false;
  
  // Handle Date objects
  if (a instanceof Date && b instanceof Date) {
    return a.getTime() === b.getTime();
  }
  
  // Handle RegExp objects
  if (a instanceof RegExp && b instanceof RegExp) {
    return a.source === b.source && a.flags === b.flags;
  }
  
  // Handle Arrays
  if (Array.isArray(a) && Array.isArray(b)) {
    if (a.length !== b.length) return false;
    for (let i = 0; i < a.length; i++) {
      if (!deepEqual(a[i], b[i])) return false;
    }
    return true;
  }
  
  // One is array, other is not
  if (Array.isArray(a) || Array.isArray(b)) return false;
  
  // Handle Set objects
  if (a instanceof Set && b instanceof Set) {
    if (a.size !== b.size) return false;
    for (const value of a) {
      let found = false;
      for (const otherValue of b) {
        if (deepEqual(value, otherValue)) {
          found = true;
          break;
        }
      }
      if (!found) return false;
    }
    return true;
  }
  
  // Handle Map objects
  if (a instanceof Map && b instanceof Map) {
    if (a.size !== b.size) return false;
    for (const [key, value] of a) {
      let found = false;
      for (const [otherKey, otherValue] of b) {
        if (deepEqual(key, otherKey) && deepEqual(value, otherValue)) {
          found = true;
          break;
        }
      }
      if (!found) return false;
    }
    return true;
  }
  
  // Different object types
  if (a.constructor !== b.constructor) return false;
  
  // Handle plain objects
  const keysA = Object.keys(a as object);
  const keysB = Object.keys(b as object);
  
  if (keysA.length !== keysB.length) return false;
  
  for (const key of keysA) {
    if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
    if (!deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])) return false;
  }
  
  return true;
}

// Deep equal with circular reference detection

Usage Examples

Basic object and array deep comparison

const obj1 = {
  user: {
    name: 'John',
    age: 30,
    hobbies: ['reading', 'coding'],
    settings: { theme: 'dark', notifications: true }
  }
};

const obj2 = {
  user: {
    name: 'John',
    age: 30,
    hobbies: ['reading', 'coding'],
    settings: { theme: 'dark', notifications: true }
  }
};

const obj3 = {
  user: {
    name: 'John',
    age: 30,
    hobbies: ['reading', 'gaming'], // Different hobby
    settings: { theme: 'dark', notifications: true }
  }
};

console.log(obj1 === obj2);           // false (different references)
console.log(deepEqual(obj1, obj2));   // true (same structure and values)
console.log(deepEqual(obj1, obj3));   // false (different hobbies)

Comparing different JavaScript data types

const complex1 = {
  date: new Date('2024-01-01'),
  regex: /test\d+/gi,
  set: new Set([1, 2, 3]),
  map: new Map([['key1', 'value1'], ['key2', 'value2']]),
  nested: { array: [1, 2, { id: 1 }] }
};

const complex2 = {
  date: new Date('2024-01-01'),
  regex: /test\d+/gi,
  set: new Set([3, 2, 1]), // Different order, but sets are unordered
  map: new Map([['key2', 'value2'], ['key1', 'value1']]), // Different order
  nested: { array: [1, 2, { id: 1 }] }
};

const complex3 = {
  date: new Date('2024-01-02'), // Different date
  regex: /test\d+/gi,
  set: new Set([1, 2, 3]),
  map: new Map([['key1', 'value1'], ['key2', 'value2']]),
  nested: { array: [1, 2, { id: 1 }] }
};

console.log(deepEqual(complex1, complex2)); // true (logically equal)
console.log(deepEqual(complex1, complex3)); // false (different date)

Handling circular references in comparison

const obj1: any = { name: 'parent1' };
obj1.child = { name: 'child1', parent: obj1 };

const obj2: any = { name: 'parent1' };
obj2.child = { name: 'child1', parent: obj2 };

const obj3: any = { name: 'parent2' }; // Different name
obj3.child = { name: 'child1', parent: obj3 };

// This would cause infinite recursion with basic deepEqual
// console.log(deepEqual(obj1, obj2)); // Stack overflow!

// Safe comparison with circular reference detection
console.log(deepEqualWithCircularCheck(obj1, obj2)); // true
console.log(deepEqualWithCircularCheck(obj1, obj3)); // false

// Verify structure is intact
console.log(obj1.child.parent === obj1); // true
console.log(obj2.child.parent === obj2); // true

Choosing the right equality method for your use case

const obj1 = { id: 1, values: [1, 2, 3, 4, 5] };
const obj2 = { id: 1, values: [1, 2, 3, 4, 5] };
const obj3 = { values: [1, 2, 3, 4, 5], id: 1 }; // Different property order

// Shallow equality (fast, but only checks top level)
console.log(shallowEqual(obj1, obj2));     // false (different array references)
console.log(shallowEqual(obj1.values, obj2.values)); // false (different references)

// Deep equality (thorough, handles nested structures)
console.log(deepEqual(obj1, obj2));        // true (same structure and values)
console.log(deepEqual(obj1, obj3));        // true (property order doesn't matter)

// Strict deep equality (considers property order)
console.log(deepEqualStrict(obj1, obj2));  // true
console.log(deepEqualStrict(obj1, obj3));  // false (different property order)

// JSON equality (fast but limited - no Dates, RegExp, etc.)
console.log(deepEqualJSON(obj1, obj2));    // true
console.log(deepEqualJSON(obj1, obj3));    // false (property order matters in JSON)

// Use cases:
// - shallowEqual: React props comparison, simple object checks
// - deepEqual: General purpose deep comparison
// - deepEqualStrict: When property order matters
// - deepEqualJSON: Fast comparison of serializable data