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 detectionUsage 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); // trueChoosing 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