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:

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.

  1. Iteratee Computation: An item is evaluated through the iteratee, returning a key.
  2. Lookup: Lodash queries the internal cache to check whether this key has already been stored.
  3. 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.
  4. 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');
  1. The elements are processed in the order: { id: 1 }, { id: 2 }, { id: 2 }, { id: 3 }.
  2. The extracted keys are 1, 2, 2, and 3.
  3. Keys 1 and 2 are added to the cache, and their corresponding objects from array1 are appended to the result.
  4. When processing { 'id': 2, 'name': 'Beta Updated' }, the key 2 matches an existing entry in the cache. The object is skipped.
  5. Key 3 is 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' }
]