isEqual
Performs a deep equality check between two values. Handles nested objects, arrays, primitives, and circular references.
1/**
2 * Performs a deep equality check between two values.
3 * Handles nested objects, arrays, primitives, and circular references.
4 *
5 * @param a - First value to compare.
6 * @param b - Second value to compare.
7 * @returns True if values are deeply equal, false otherwise.
8 */
9export function isEqual(a: any, b: any): boolean {
10 const seen = new WeakMap();
11
12 function deepCompare(x: any, y: any): boolean {
13 if (Object.is(x, y)) return true;
14
15 if (typeof x !== 'object' || x === null ||
16 typeof y !== 'object' || y === null) {
17 return false;
18 }
19
20 if (seen.get(x) === y) return true;
21 seen.set(x, y);
22
23 const xKeys = Object.keys(x);
24 const yKeys = Object.keys(y);
25
26 if (xKeys.length !== yKeys.length) return false;
27
28 for (const key of xKeys) {
29 if (!yKeys.includes(key)) return false;
30 if (!deepCompare(x[key], y[key])) return false;
31 }
32
33 return true;
34 }
35
36 return deepCompare(a, b);
37}
Deep Structural Comparison
Recursively compares nested objects and arrays, going beyond shallow equality checks like
===
orObject.is
.Handles Circular References
Uses a
WeakMap
to detect and safely compare cyclic structures, preventing infinite recursion.Broad Type Support
Correctly handles primitives, objects, arrays,
null
, andundefined
, making it robust for diverse data shapes.Deterministic and Pure
Produces consistent results without side effects, ideal for use in testing, memoization, or UI diffing.
Accurate Key Comparison
Verifies both keys and values, ensuring true deep equality rather than partial similarity.
Tests | Examples
1test('isEqual - compares primitive values', () => {
2 expect(isEqual(1, 1)).toBe(true);
3 expect(isEqual('a', 'a')).toBe(true);
4 expect(isEqual(null, null)).toBe(true);
5 expect(isEqual(undefined, undefined)).toBe(true);
6 expect(isEqual(1, 2)).toBe(false);
7});
8
9test('isEqual - compares simple objects', () => {
10 expect(isEqual({ a: 1 }, { a: 1 })).toBe(true);
11 expect(isEqual({ a: 1 }, { a: 2 })).toBe(false);
12});
13
14test('isEqual - compares nested objects', () => {
15 const a = { a: { b: [1, 2, 3] } };
16 const b = { a: { b: [1, 2, 3] } };
17 expect(isEqual(a, b)).toBe(true);
18});
19
20test('isEqual - detects key mismatch', () => {
21 expect(isEqual({ a: 1 }, { b: 1 })).toBe(false);
22});
23
24test('isEqual - compares arrays', () => {
25 expect(isEqual([1, 2], [1, 2])).toBe(true);
26 expect(isEqual([1, 2], [2, 1])).toBe(false);
27});
28
29test('isEqual - handles circular references', () => {
30 const a: any = { foo: 1 };
31 a.self = a;
32
33 const b: any = { foo: 1 };
34 b.self = b;
35
36 expect(isEqual(a, b)).toBe(true);
37});
38
39test('isEqual - returns false for mismatched types', () => {
40 expect(isEqual({ a: 1 }, [{ a: 1 }])).toBe(false);
41});
42
Common Use Cases
Testing and Assertions
Verify deep equality of expected vs. actual data in unit tests or snapshot testing.
Memoization and Caching
Detect changes in deeply nested structures to avoid unnecessary recomputations.
UI Rendering Optimization
Compare props or state objects to prevent unnecessary re-renders in component libraries like React.
Form or State Change Detection
Check whether deeply nested user input or state data has changed from its original version.
Diffing or Syncing Systems
Determine whether two structured documents or data trees are equivalent before syncing or applying changes.