Hone logo
Hone
Problems

JavaScript Object Equality Checker

Comparing JavaScript objects for equality can be trickier than it seems. Unlike primitive types (like numbers or strings) where === does a direct value comparison, === on objects checks for reference equality – meaning they are the exact same object in memory. This challenge requires you to implement a function that performs a deep comparison of two JavaScript objects, checking if they have the same keys and values, recursively. This is a fundamental skill for testing, state management, and data manipulation in JavaScript.

Problem Description

Your task is to create a JavaScript function named deepEqual that accepts two arguments, obj1 and obj2, and returns true if they are deeply equal, and false otherwise.

Key Requirements:

  • The function must recursively compare nested objects and arrays.
  • It should handle primitive types correctly (numbers, strings, booleans, null, undefined, symbols, bigints).
  • It should consider the order of keys in objects when comparing (i.e., { a: 1, b: 2 } is not equal to { b: 2, a: 1 } unless you decide to ignore key order, which for this challenge, we will not).
  • It should consider the order of elements in arrays.
  • The function should handle circular references gracefully, preventing infinite recursion.

Expected Behavior:

  • If obj1 and obj2 are strictly equal (===), return true.
  • If obj1 or obj2 are not objects or are null, compare them using strict equality.
  • If they are arrays, compare their lengths and then recursively compare each element.
  • If they are plain objects, compare the number of keys. Then, compare each key-value pair from obj1 with the corresponding key-value pair in obj2.

Edge Cases to Consider:

  • Comparing an object with an array.
  • Comparing objects with null or undefined.
  • Comparing objects with different types of values (e.g., number vs. string).
  • Objects with no keys.
  • Arrays with no elements.
  • Circular references within objects or arrays.

Examples

Example 1:

Input: obj1 = { a: 1, b: { c: 3 } }, obj2 = { a: 1, b: { c: 3 } }
Output: true
Explanation: Both objects have the same keys and nested values in the same order.

Example 2:

Input: obj1 = { a: 1, b: 2 }, obj2 = { b: 2, a: 1 }
Output: false
Explanation: The order of keys differs, and for this challenge, key order matters.

Example 3:

Input: obj1 = [1, [2, 3]], obj2 = [1, [2, 3]]
Output: true
Explanation: Both arrays have the same elements in the same order, including nested arrays.

Example 4:

Input: obj1 = { a: 1 }, obj2 = { a: '1' }
Output: false
Explanation: The value for key 'a' has different types (number vs. string).

Example 5:

Input: obj1 = 5, obj2 = 5
Output: true
Explanation: Primitive values are compared using strict equality.

Example 6:

Input: obj1 = { a: 1 }, obj2 = null
Output: false
Explanation: Comparing an object with null.

Example 7:

Input: obj1 = {}, obj2 = {}
Output: true
Explanation: Two empty objects are considered equal.

Example 8: (Circular Reference)

Input:
let circularObj1 = { name: 'obj1' };
circularObj1.self = circularObj1;

let circularObj2 = { name: 'obj1' };
circularObj2.self = circularObj2;

Output: true
Explanation: The circular references are handled correctly, recognizing that both objects point back to themselves in the same manner.

Example 9: (Different Circular Reference)

Input:
let circularObj1 = { name: 'obj1' };
circularObj1.self = circularObj1;

let circularObj2 = { name: 'obj2' };
circularObj2.self = circularObj2;

Output: false
Explanation: Although circular, the top-level objects have different properties.

Constraints

  • The function should be able to handle objects and arrays containing primitive types, nested objects, and nested arrays.
  • The comparison should be case-sensitive for string values.
  • Performance is important; the solution should avoid unnecessary iterations. For very large, deeply nested objects, extremely inefficient solutions might time out.
  • The function should return false immediately if a mismatch is found at any level of recursion.

Notes

  • Consider how you will differentiate between plain objects and other types of objects (like Dates, Regex, etc.). For this challenge, focus on "plain" JavaScript objects ({}) and arrays ([]).
  • A common approach for handling circular references involves using a Set or Map to keep track of objects that have already been compared during the current comparison path.
  • Think about the order of checks. For example, checking for strict equality first can be an optimization.
  • When comparing object keys, ensure you are comparing the exact keys present in both objects.
Loading editor...
javascript