How Lodash Shuffle Handles Array Mutation
Lodash’s _.shuffle method uses the Fisher-Yates shuffle
algorithm while strictly preserving data immutability for the input
collection. To prevent modifying the original array, Lodash first
creates an internal shallow copy of the source elements and executes the
element-swapping routine entirely within that isolated copy. This
approach combines the performance advantages of an in-place Fisher-Yates
permutation with the safety of a non-destructive functional API.
The Defensive Copy
When _.shuffle receives an array or collection, it does
not apply mutations directly to the target reference. Instead, the
method invokes an internal utility—historically copyArray
or toArray depending on whether the input is a native array
or an iterable object.
By allocating a new array populated with shallow references to the original elements, Lodash establishes a disposable memory buffer. The original array remains untouched in its original memory location, ensuring that callers do not experience side effects.
Internal Fisher-Yates Mechanics
Once the cloned array is established, control passes to Lodash’s
internal shuffling mechanism (often structured as
baseShuffle). Lodash employs the modern version of the
Fisher-Yates algorithm (also known as the Knuth or Durstenfeld
shuffle).
The algorithm operates linearly over the copied array:
- It tracks the current iteration index and determines the bounds for random selection.
- For each element at index
i, it calculates a pseudorandom indexrandwithin the valid range usingMath.random(). Depending on whether the loop moves forward or backward,randfalls between0andi, or betweeniandlength - 1. - It performs a classic swap: the value currently occupying the target index is stored temporarily, the randomly chosen element replaces it, and the stored value is written to the random index.
Local Mutation for Performance
While the public-facing API behaves immutably, the internal
implementation intentionally uses mutable assignment operators on the
local buffer. Instead of creating new arrays on each swap (such as using
.slice() or spread syntax), Lodash writes directly to array
indices (result[index] = result[rand]).
Performing direct indexed writes on the cloned array maintains an optimal time complexity of \(O(n)\) and minimizes garbage collection overhead. Once the iteration completes and every element has had an equal probability of landing in any position, Lodash returns the mutated local copy as the final result.