How Lodash unionBy Enforces Uniqueness Across Arrays
Lodash’s _.unionBy method combines multiple arrays into
a single array of unique elements based on a specified criterion, known
as an iteratee. This article explores the internal mechanics of
_.unionBy, explaining how it processes input arrays,
evaluates iteratee criteria, manages an internal cache of computed keys,
and determines element uniqueness using equality algorithms to preserve
only the first occurrence of each item.
Sequential Processing and Order of Precedence
When _.unionBy is called, it accepts one or more arrays
followed by an iteratee argument at the end. Internally, Lodash flattens
the supplied arrays into a single sequential list using an internal
helper (baseFlatten).
Because it iterates through this flattened list from left to right,
order of precedence is strictly maintained. When two or more elements
yield an identical comparison key, _.unionBy retains the
element that appeared first and discards all subsequent duplicates.
Iteratee Evaluation
The iteratee defines the identity of an element. Lodash normalizes
the iteratee using its baseIteratee utility, which supports
several formats:
- Function: A custom callback executed on each
element (e.g.,
item => item.id). - Property Path (String): Shorthand for extracting a
property value (e.g.,
'id'or'nested.property'). - Object / Matches: Property-value pairs to match against.
For every element processed, Lodash invokes the iteratee once to extract a derived comparison value. The original element itself is kept for insertion into the output, while the derived value is used exclusively for uniqueness checks.
Key Storage and SetCache
To determine if an element is unique without incurring an \(O(n^2)\) time complexity, Lodash tracks
already encountered values using an internal structure known as
SetCache.
- Iteratee Computation: An item is evaluated through the iteratee, returning a key.
- Lookup: Lodash queries the internal cache to check whether this key has already been stored.
- Storage and Output: If the key is absent from the
cache:
- The key is added to the cache.
- The original item is pushed to the final output array.
- Disregard: If the key already exists in the cache, the item is ignored, and the loop advances to the next entry.
In modern JavaScript environments, SetCache delegates to
the native Set or Map implementations where
appropriate, providing near \(O(1)\)
lookup performance for each element.
SameValueZero Equality
Lodash checks for value equality within its cache using the
SameValueZero comparison algorithm. Unlike strict equality
(===), SameValueZero treats NaN
as equal to NaN. It also treats +0 and
-0 as equal.
If an iteratee computes to NaN for two separate items,
_.unionBy treats them as duplicates and only outputs the
first item.
Practical Demonstration
Consider the following example:
const array1 = [{ 'id': 1, 'name': 'Alpha' }, { 'id': 2, 'name': 'Beta' }];
const array2 = [{ 'id': 2, 'name': 'Beta Updated' }, { 'id': 3, 'name': 'Gamma' }];
const result = _.unionBy(array1, array2, 'id');- The elements are processed in the order:
{ id: 1 },{ id: 2 },{ id: 2 },{ id: 3 }. - The extracted keys are
1,2,2, and3. - Keys
1and2are added to the cache, and their corresponding objects fromarray1are appended to the result. - When processing
{ 'id': 2, 'name': 'Beta Updated' }, the key2matches an existing entry in the cache. The object is skipped. - Key
3is new, so it is added to the cache and the object is appended to the result.
The final result preserves the exact object references from the first occurrence:
[
{ 'id': 1, 'name': 'Alpha' },
{ 'id': 2, 'name': 'Beta' },
{ 'id': 3, 'name': 'Gamma' }
]