Javascript Interview Question: Write a function that deep compares two objects

Explore the Inner Workings of deepCompare Function for Accurate Object Equality Checks

Why we need deep compare function?

The === equality operator in JavaScript checks for reference equality, not structural equality. If you have two objects with the same structure and values but are different instances, === would consider them unequal. A deep comparison function allows you to check for structural equality.

function deepCompare(obj1, obj2) {
// Check if both parameters are objects
if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
return obj1 === obj2;
}

// Get the keys of each object
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);

// Check if the number of keys is the same
if (keys1.length !== keys2.length) {
return false;
}

// Iterate through keys and recursively compare values
for (const key of keys1) {
if (!keys2.includes(key) || !deepCompare(obj1[key], obj2[key])) {
return false;
}
}

// If all checks passed, the objects are deeply equal
return true;
}

Now Let’s break down each line:

1 Function Declaration:

function deepCompare(obj1, obj2) {

This declares a function named deepCompare that takes two parameters, obj1 and obj2, representing the objects to be compared.

2 Type and Null Check:

if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
return obj1 === obj2;
}

This part of the function checks the types and null values of obj1 and obj2. The purpose of this check is to handle the base case of the recursion and also to handle the scenario where one or both of the objects are not actually objects.

  • typeof obj1 !== 'object' || obj1 === null: This checks if obj1 is not of type 'object' or if it is null. If either condition is true, it means obj1 is not an object, and the function compares obj1 and obj2 directly (return obj1 === obj2).
  • typeof obj2 !== 'object' || obj2 === null: Similarly, this checks if obj2 is not of type 'object' or if it is null. If either condition is true, it means obj2 is not an object, and the function compares obj1 and obj2 directly (return obj1 === obj2).

The purpose of these checks is to handle scenarios where the function is called with non-object values or null. In such cases, there is no need to perform a deep comparison, and the function can directly compare the values.

3 Get Keys of Objects:

const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);

These lines retrieve the keys of each object using Object.keys().

4 Check Number of Keys:

if (keys1.length !== keys2.length) {
return false;
}
This checks if the number of keys in both objects is the same. If not, the objects cannot be deeply equal, and the function returns false.

5 Recursive Comparison:

for (const key of keys1) {
if (!keys2.includes(key) || !deepCompare(obj1[key], obj2[key])) {
return false;
}
}

This loop iterates through the keys of the first object (keys1). For each key, it checks if the second object (obj2) contains the same key and recursively compares the values using deepCompare. If any comparison returns false, the function immediately returns false.

6 Final Return:

return true;

If all checks pass, the function returns true, indicating that the objects are deeply equal.

This line is the final return statement of the function. If the function has passed all the previous checks and iterations without returning false, it means that the two objects are deeply equal. Therefore, the function returns true.

  • If the function reaches this point, it indicates that all the key-value pairs in the objects (and their nested objects) have been successfully compared, and none of the comparisons resulted in a mismatch.
  • Returning true signals that the two objects are deeply equal, satisfying the conditions for a deep comparison.

Usage

const objA = { a: 1, b: { c: 2 } };
const objB = { a: 1, b: { c: 2 } };
const objC = { a: 1, b: { c: 3 } };

console.log(deepCompare(objA, objB)); // Output: true
console.log(deepCompare(objA, objC)); // Output: false

Downside of above function:

1. Circular References:

If the objects being compared contain circular references (i.e., an object references itself), the function may enter into an infinite loop and cause a stack overflow. Handling circular references requires additional logic to keep track of visited objects.

2. Prototype Chain:

The function does not consider the prototype chain. If objects have properties defined in their prototypes, those properties won't be included in the comparison. To include prototype properties, additional logic is needed to traverse the prototype chain.

3. Non-Enumerable Properties:

Properties that are not enumerable (e.g., those created with Object.defineProperty with enumerable: false) will not be included in the comparison. This might lead to unexpected results if you rely on non-enumerable properties.

4. Sparse Arrays:

Sparse arrays (arrays with holes, i.e., undefined values at certain indices) might not be handled correctly. The function treats an array with a hole as if it's a shorter array, potentially leading to incorrect comparisons.

5. Date Objects:

Date objects are not handled specifically in this function. If you need to compare Date objects, you might want to add special handling for them.

6. Function Properties:

If the objects being compared contain function properties, the function does not compare the function bodies. It only checks if the function references are the same.

7 .Performance for Large Objects:

The function may not be optimal for very large objects due to its recursive nature. For extremely deep or complex objects, it might result in a large number of recursive calls.

Conclusion: This answer might help you to clear your interview. But in production consider using Loadash or any other good third party library.

--

--

राहुल मिश्रा
राहुल मिश्रा

No responses yet