Shallow Equality vs Deep Equality in JavaScript
In JavaScript, comparing two values for equality depends heavily on their underlying data types and structure. Understanding the distinction between shallow equality and deep equality is essential for effective state management, performance optimization, and bug prevention. While shallow equality compares primitive values directly and complex objects by memory reference (or by their first-level properties only), deep equality recursively traverses nested structures to verify that every nested key and value matches across both items.
Understanding Shallow Equality
Shallow equality compares values at the surface level. For primitive
data types (such as numbers, strings, and booleans), JavaScript compares
them by their actual value using operators like === or
Object.is.
However, objects, arrays, and functions in JavaScript are reference
types. When using the strict equality operator (===),
JavaScript checks whether both operands point to the exact same location
in memory, not whether their contents are identical.
// Primitive comparison
const a = 5;
const b = 5;
console.log(a === b); // true
// Reference comparison
const obj1 = { name: "Alice" };
const obj2 = { name: "Alice" };
console.log(obj1 === obj2); // false (different memory references)
const obj3 = obj1;
console.log(obj1 === obj3); // true (same memory reference)In libraries and frameworks like React, shallow equality often refers
to a shallow property check. This means iterating over the immediate
keys of two objects and verifying that the value of each key is strictly
equal (===) to the corresponding key in the other object.
If an object contains nested objects, only their references are
compared, not their internal values.
Understanding Deep Equality
Deep equality checks whether two entities have identical contents, regardless of their memory references or how deeply nested their structures are. To determine deep equality, an algorithm must recursively traverse every property, array element, and nested object, comparing each nested leaf node for value equality.
const userA = {
name: "Alice",
address: { city: "New York", zip: 10001 }
};
const userB = {
name: "Alice",
address: { city: "New York", zip: 10001 }
};
// Shallow check: false (userA.address !== userB.address)
// Deep check: true (all nested keys and values match)JavaScript does not have a built-in operator for deep equality. Common ways to perform deep equality checks include:
- Utility Libraries: Functions like
isEqualfrom Lodash or Underscore handle edge cases including circular references, dates, and regular expressions. - Testing Matchers: Assertion libraries in frameworks
like Jest offer matchers such as
toEqual()for deep comparisons. - Custom Recursion: Writing a custom recursive
function to iterate through
Object.keys()and nested sub-objects. - JSON Serialization: Using
JSON.stringify(a) === JSON.stringify(b). While fast for simple data, this approach is unreliable if object keys are ordered differently or if values containundefined, functions,NaN, orDateobjects.
Key Differences
| Feature | Shallow Equality | Deep Equality |
|---|---|---|
| Depth of Check | Top-level properties only | All nested levels recursively |
| Performance | Fast (\(O(1)\) or \(O(k)\) where \(k\) is top-level keys) | Slower (\(O(n)\) where \(n\) is total nested elements) |
| Memory Comparison | Compares object references | Ignores references, compares nested data |
| Native Support | Built-in (===,
Object.is) |
Requires custom logic or external libraries |
When to Use Each
- Use Shallow Equality: When working with flat data
structures, performing performance-critical checks, or managing UI
updates in libraries like React (
React.memo,shouldComponentUpdate), where components rely on immutable data patterns. - Use Deep Equality: When comparing complex API responses, writing automated test assertions, or verifying form state changes where data is deeply nested and reference immutability is not guaranteed.