Lodash invertBy Grouping Logic for Duplicate Values

This article examines the structural grouping logic used by the Lodash library's _.invertBy method to handle duplicate values. It details how the function traverses source object properties, dynamically generates inverted target keys, and constructs array buckets to accumulate identical values without overwriting previous data.

The Core Inversion Mechanism

In standard dictionary inversion, keys and values swap positions directly. When multiple keys share the identical string value, a naive inversion leads to property overwriting, retaining only the last processed key. Lodash resolves this through _.invertBy(object, [iteratee=_.identity]), which groups conflicting keys into an array rather than storing a single scalar key.

Internal Grouping Logic

The dynamic mapping of duplicate values operates through an accumulator-based aggregation pattern:

  1. Object Traversal: Lodash iterates over the object's own enumerable string keyed properties using its internal object iteration methods.
  2. Key Derivation via Iteratee: For each property, the current value is passed to the iteratee function (defaulting to _.identity). The returned value is coerced to a string to serve as the new key on the output object.
  3. Collision Detection via hasOwnProperty: Lodash checks whether the derived key already exists on the accumulator object using Object.prototype.hasOwnProperty.
  4. Dynamic Bucket Initialization and Appending:
    • If the key does not exist, the accumulator initializes a new array containing the current source key: result[groupKey] = [sourceKey].
    • If the key already exists, Lodash pushes the current source key into the pre-existing array: result[groupKey].push(sourceKey).

Code Representation

The underlying algorithm follows this structure:

const source = {
  a: 'alpha',
  b: 'beta',
  c: 'alpha'
};

// Logical equivalent of _.invertBy
const result = {};

for (const key of Object.keys(source)) {
  const value = source[key];
  const groupKey = String(value); // transformed by iteratee if provided

  if (Object.prototype.hasOwnProperty.call(result, groupKey)) {
    result[groupKey].push(key);
  } else {
    result[groupKey] = [key];
  }
}

// Result: { alpha: ['a', 'c'], beta: ['b'] }

Array Growth and Dynamic Mutation

By using native array references within the accumulator object, _.invertBy dynamically scales storage per unique value. When subsequent duplicate string values appear during traversal, the reference to the existing bucket is retrieved in \(O(1)\) average time, and the new key is appended via an array push operation. This produces a partitioned map of arrays representing an inverted index of the source collection.