Lodash countBy Key Coercion Explained

Lodash’s _.countBy method processes a collection, runs each element through an iteratee function, and returns an object counting the frequency of each computed result. Because object keys in JavaScript are fundamentally strings or symbols, any value returned by the iteratee must be converted into a valid property key. This article examines the internal mechanics of how Lodash and JavaScript coerce these returned values into object keys.

The Aggregator Pipeline

Under the hood, _.countBy is implemented using Lodash’s internal createAggregator utility. This higher-order function manages collection traversal and accumulates results into a newly created plain object.

For each item in the collection, the aggregator invokes the provided iteratee:

const key = iteratee(value);

Once the iteratee returns a value, Lodash registers the occurrence on the accumulator object using an internal assignment function (baseAssignValue), effectively executing:

if (hasOwnProperty.call(result, key)) {
  result[key]++;
} else {
  baseAssignValue(result, key, 1);
}

JavaScript's ToPropertyKey Operation

Lodash does not manually convert iteratee outputs into strings using explicit methods like .toString() or String(key) within _.countBy. Instead, it delegates key coercion directly to the JavaScript runtime engine.

Whenever an expression is evaluated inside bracket notation (such as result[key]), the JavaScript engine executes the abstract ECMAScript operation ToPropertyKey(key). This operation determines how different data types are transformed:

Coercion in Practice

Consider the following example demonstrating various returned iteratee values:

const items = [1, 1.5, null, undefined, [2, 3], {}];

const result = _.countBy(items, (item) => item);

The resulting object reflects automatic runtime coercion:

{
  "1": 1,
  "1.5": 1,
  "null": 1,
  "undefined": 1,
  "2,3": 1,
  "[object Object]": 1
}

Because coercion relies on ToPropertyKey, all complex objects that do not override their default toString method collapse into the single key '[object Object]'. To avoid unintended key collisions when working with complex return types, iteratees should explicitly return unique, primitive identifiers like strings or numbers.