How Lodash sample Generates Random Numbers

The Lodash _.sample function selects a single random element from an array by delegating index selection to an internal helper that relies on JavaScript's native Math.random() method. Rather than implementing its own proprietary PRNG (pseudo-random number generator) algorithm, Lodash normalizes bounds and maps floating-point numbers to integers, relying directly on the host engine's PRNG—predominantly the xorshift128+ algorithm in modern environments like V8. This article explores how _.sample calculates random indices and details the mathematical mechanics of the underlying PRNG engine.

The Lodash Implementation Layer

When you pass an array to _.sample, Lodash executes a concise call stack to extract an item:

  1. Length Validation: The method checks the collection's length. If the array is empty or null, it returns undefined.
  2. Index Generation: For non-empty collections, it calls the internal utility function baseRandom(lower, upper).
  3. Range Calculation: The random index is obtained by setting lower = 0 and upper = length - 1.

The source logic inside Lodash's baseRandom performs the standard range-mapping calculation:

function baseRandom(lower, upper) {
  return lower + Math.floor(Math.random() * (upper - lower + 1));
}

This formula scales the output of Math.random(), which produces a floating-point value in the half-open interval \([0, 1)\), across the discrete integer range of array indices \([0, \text{length} - 1]\). The operation Math.floor truncates the fractional part, ensuring each valid index has an equal probability of selection:

\[P(i) = \frac{1}{\text{length}}\]

The Delegation to Native PRNGs

Lodash does not maintain an internal random seed or implement custom mathematical PRNG algorithms like a Linear Congruential Generator (LCG) or Mersenne Twister. Instead, it fully delegates generation to the JavaScript runtime environment via Math.random().

Historically, early JavaScript runtimes used basic PRNGs, such as Multiply-With-Carry (MWC1616), which suffered from short period lengths and poor statistical entropy. To correct this, major JavaScript engines standardized on more robust non-cryptographic PRNGs. In modern V8 engines (Chrome, Node.js), Math.random() is powered by the xorshift128+ algorithm.

The Math Behind xorshift128+

The xorshift128+ PRNG operates on an internal state composed of two 64-bit unsigned integers, providing an overall state space of 128 bits. The algorithm generates a sequence of pseudo-random numbers using linear operations (bitwise XOR and bit-shifts) combined with non-linear addition.

State Transition Mechanics

With an internal state represented as \(s_0\) and \(s_1\), the generator updates the state via the following sequence of transformations per step:

  1. Let \(a = s_0\)
  2. Let \(b = s_1\)
  3. Update \(s_0 = b\)
  4. Apply bitwise shifts and XOR operations to \(a\): \[a \leftarrow a \oplus (a \ll 23)\] \[a \leftarrow a \oplus (a \gg 17)\] \[a \leftarrow a \oplus b \oplus (b \gg 26)\]
  5. Update \(s_1 = a\)

The result of the step is then calculated by adding the state variables:

\[\text{Output} = (s_0 + s_1) \pmod{2^{64}}\]

Conversion to Floating-Point

Because Math.random() returns a double-precision float in the range \([0, 1)\), the 64-bit integer generated by xorshift128+ is mapped directly into an IEEE 754 floating-point representation.

A standard 64-bit float contains a 52-bit mantissa (fraction). The engine takes the upper 52 bits of the generated integer, places them into the mantissa bits of the IEEE 754 structure with an exponent set to 1, and then subtracts 1.0. This yields a uniformly distributed floating-point value in \([0, 1)\), which Lodash immediately consumes to produce an array index.

Characteristics and Limitations