Yevhen Klymentiev
dark
light
console
darkness
y.klymentiev@gmail.com
Reusable Snippets|Practical utility code for everyday use — custom-built and ready to share

isEqual

Performs a deep equality check between two values. Handles nested objects, arrays, primitives, and circular references.

TypeScript
Copied!
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 === or Object.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, and undefined, 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

TypeScript
Copied!
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.

Codebase: Utilities -> Objects -> isEqual | Yevhen Klymentiev