How Lodash _.random Ensures Uniform Distribution

The _.random function in Lodash provides uniform pseudo-random number generation by combining modern JavaScript engine PRNGs with precise interval scaling and boundary-safe transformations. Rather than implementing its own proprietary generator from scratch, Lodash delegates entropy generation to JavaScript's native Math.random() and applies mathematical mappings that prevent boundary skew and modulo bias. This article examines the internal mechanisms Lodash uses to ensure both integer and floating-point distributions remain strictly uniform across arbitrary ranges.

Reliance on Modern Engine PRNGs

Lodash relies on JavaScript's native Math.random() as its entropy source. Modern JavaScript engines, such as V8 (Node.js, Chrome) and SpiderMonkey (Firefox), implement high-quality pseudo-random number generators (PRNGs), typically variants of the xorshift128+ algorithm.

These algorithms generate double-precision floating-point numbers uniformly distributed in the half-open interval \([0, 1)\), meaning:

\[0 \le x < 1\]

Because the underlying engine guarantees that every sub-interval within \([0, 1)\) has an equal likelihood of being chosen, Lodash’s primary responsibility is to preserve this property when transforming the unit interval to the caller's target range [lower, upper].

Eliminating Modulo Bias for Integers

A common mistake in pseudo-random mapping is using the modulo operator (e.g., randomInt % range), which causes modulo bias when the generator's state space does not divide evenly into the target range. Lodash eliminates modulo bias entirely by using interval scaling and floor discretization:

lower + nativeFloor(nativeRandom() * (upper - lower + 1))

This mathematical approach guarantees uniformity through three distinct steps:

  1. Interval Sizing (upper - lower + 1): When generating inclusive integers, the total number of distinct outcomes is \(N = upper - lower + 1\). Multiplying the \([0, 1)\) interval by \(N\) expands the continuous range to \([0, N)\).
  2. Equidistant Partitioning: The interval \([0, N)\) is partitioned into \(N\) equal-width bins of length 1: \([0, 1), [1, 2), \dots, [N - 1, N)\). Since the underlying PRNG is uniform, the probability of a value falling into any single bin is precisely:

\[P(\text{bin}_k) = \frac{1}{N}\]

  1. Floor Discretization (nativeFloor): Applying Math.floor() maps each continuous bin directly to its corresponding integer index without compressing or stretching the endpoints. Adding lower shifts the zero-indexed bin into the requested output domain.

Floating-Point Normalization and Inclusive Bounds

When floating-point values are requested (either via the floating flag or passing float arguments), Lodash generates a continuous real number:

var rand = nativeRandom();
var randLength = rand.toString().length - 1;
return Math.min(lower + (rand * (upper - lower + freeParseFloat('1e-' + randLength))), upper);

Because native Math.random() produces values in the half-open range \([0, 1)\), the upper bound is technically unreachable under direct multiplication. To make the range inclusive for floating-point values without distorting the linear distribution, Lodash applies an epsilon-scaled offset based on the string precision of the generated float and clamps the result to upper using Math.min(). This ensures that intermediate numbers scale linearly while safely accounting for edge inclusiveness.

Argument Normalization and Deterministic Boundaries

To prevent statistical distortions caused by invalid parameters or undefined state, Lodash normalizes inputs before executing the mathematical transformation:

By enforcing linear scaling over raw modulo arithmetic and relying on native double-precision PRNG implementations, Lodash guarantees that each value within the user-specified interval maintains an identical probability of selection.