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:

  1. It tracks the current iteration index and determines the bounds for random selection.
  2. For each element at index i, it calculates a pseudorandom index rand within the valid range using Math.random(). Depending on whether the loop moves forward or backward, rand falls between 0 and i, or between i and length - 1.
  3. 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.