How Lodash Values Sorts Unordered Numeric Keys

This article examines how the Lodash utility function _.values structurally orders elements when extracting values from an object containing unordered numeric keys. It details the interaction between Lodash's internal property traversal methods and the ECMAScript specifications that dictate key enumeration order in JavaScript runtimes.

When you pass an object with out-of-order numeric keys to _.values, the resulting array contains the values sorted in ascending numerical order of those keys, regardless of their original insertion order. For example, passing { '10': 'c', '2': 'b', '1': 'a' } into _.values returns ['a', 'b', 'c'].

The ECMAScript OrdinaryOwnPropertyKeys Specification

Lodash does not implement a custom sorting algorithm inside _.values. Instead, the observed sorting behavior is a direct consequence of how modern JavaScript engines enumerate object properties according to the ECMAScript 2015 (ES6) specification and its subsequent revisions.

Under the ES specification (specifically the internal method [[OwnPropertyKeys]] or OrdinaryOwnPropertyKeys), property keys are ordered deterministically across three distinct phases:

  1. Integer Indices: Keys that are non-negative integer strings (indices from 0 to 2^32 - 2) are processed first, sorted in ascending numerical order.
  2. String Keys: Standard string properties are processed next, strictly in chronological insertion order.
  3. Symbol Keys: Symbol properties are processed last, also in chronological insertion order.

Because property keys that can be parsed as non-negative 32-bit integers are always placed in the first phase and sorted numerically, the runtime normalizes their sequence before Lodash retrieves them.

Internal Lodash Implementation

Internally, _.values delegates to baseValues, which extracts the values of an object corresponding to the keys returned by _.keys. The _.keys method, in turn, relies on native Object.keys() when running in modern environments, or internally traverses enumerable own properties using standard iteration protocols.

Since native implementations of Object.keys(), for...in loops, and Reflect.ownKeys() all conform to the OrdinaryOwnPropertyKeys ordering rule, Lodash retrieves the numeric keys in their canonical, ascending order. Lodash then maps each key to its corresponding property value in the exact order the keys were retrieved.

Practical Implications

When designing data structures intended for consumption via _.values, you cannot rely on insertion order if the object keys are integer-like numbers. If preserving the original insertion sequence of numeric properties is required, an array of objects or an ES6 Map should be used instead of an ordinary JavaScript object, as Map guarantees iteration based solely on entry insertion order.