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:
- Length Validation: The method checks the
collection's length. If the array is empty or null, it returns
undefined. - Index Generation: For non-empty collections, it
calls the internal utility function
baseRandom(lower, upper). - Range Calculation: The random index is obtained by
setting
lower = 0andupper = 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:
- Let \(a = s_0\)
- Let \(b = s_1\)
- Update \(s_0 = b\)
- 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)\]
- 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
- Period Length: The xorshift128+ algorithm has a
cycle period of \(2^{128} - 1\). An
application calling
_.samplewill not repeat its pseudo-random cycle under practical usage lifetimes. - Statistical Quality: It passes modern statistical test suites like BigCrush, preventing systematic bias or clustering when selecting items from a collection.
- Non-Cryptographic Nature: Because xorshift128+ is
linear, observing a short sequence of consecutive outputs allows the
full internal state to be reconstructed. Consequently,
_.sampleis not suitable for cryptographic functions, token generation, or security-sensitive shuffling.