Question

How to Shuffle a JavaScript Array with Fisher-Yates

javascriptarraysrandomshuffle

Question

Given the following JavaScript array:

const arr1 = ["a", "b", "c", "d"];

How can you randomly shuffle its elements? Explain how to shuffle the original array and, when needed, how to create a shuffled copy without changing the original array.

Short Answer

You will learn how to shuffle a JavaScript array with the Fisher-Yates algorithm, why it is preferred over sorting with a random comparator, and how to choose between mutating an array and returning a new shuffled copy.

Concept

Shuffling means rearranging every element in an array into a random order.

The standard solution is the Fisher-Yates shuffle (also called the Knuth shuffle). It walks backward through the array. At each position, it selects a random element from the portion that has not yet been fixed and swaps the two values.

This matters because a good shuffle should give every possible ordering an equal chance. For an array with four elements, there are 4! (24) possible orders. Fisher-Yates can produce those orders uniformly when given a uniform random-number source.

JavaScript's Math.random() is suitable for ordinary UI behavior, games, and randomized display order. It is not designed for security-sensitive work, such as generating passwords, cryptographic keys, or fair security tokens.

Mental Model

Imagine four cards lying on a table.

  1. Start with the last card position.
  2. Pick one card at random from all cards that have not been handled yet.
  3. Put that picked card into the current position.
  4. Move one position left and repeat.

Once a position has been filled, it is locked in place. This prevents earlier random choices from being disturbed and ensures each card gets one final position.

Take Quiz

Syntax and Examples

The Fisher-Yates algorithm uses a loop, a random index, and destructuring assignment to swap elements.

function shuffleInPlace(array) {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }

  return array;
}

const arr1 = ["a", "b", "c", "d"];
shuffleInPlace(arr1);

console.log(arr1); // Example: ["c", "a", "d", "b"]

j is chosen from 0 through i, inclusive:

const j = Math.floor(Math.random() * (i + 1));

Math.random() returns a number from 0 inclusive up to 1 exclusive. Multiplying by i + 1 creates a range from 0 up to, but not including, i + 1. Math.floor() converts it to an integer.

Return a shuffled copy

Use the spread operator before shuffling if the original array must remain unchanged:

function shuffled(array) {
  return shuffleInPlace([...array]);
}

const letters = ["a", "b", "c", "d"];
const randomLetters = shuffled(letters);

console.log(letters);       // ["a", "b", "c", "d"]
console.log(randomLetters); // Example: ["d", "b", "a", "c"]

Step by Step Execution

Consider this array:

const values = ["a", "b", "c", "d"];

A possible Fisher-Yates run looks like this:

for (let i = values.length - 1; i > 0; i--) {
  const j = Math.floor(Math.random() * (i + 1));
  [values[i], values[j]] = [values[j], values[i]];
}

Suppose the random indexes are 1, then 0, then 1.

  1. i is 3, the last index. Suppose j is 1.

    • Swap indexes 3 and 1.
    • ["a", "b", "c", "d"] becomes ["a", "d", "c", "b"].
  2. i is 2. Suppose j is 0.

    • Swap indexes 2 and 0.
    • ["a", "d", "c", "b"] becomes ["c", "d", "a", "b"].
  3. i is 1. Suppose j is 1.

    • Swap index 1 with itself.
    • The array remains ["c", "d", "a", "b"].
  4. The loop ends because index 0 is the only remaining position. The array is shuffled.

Swapping an item with itself is valid and necessary: it gives every possible final order the correct probability.

Real World Use Cases

Shuffling is useful whenever order should vary without losing or duplicating elements.

  • Quiz applications: show questions in a different order for each attempt.
  • Music players: randomize a playlist before playback.
  • Flashcard tools: mix study cards to reduce memorizing their original order.
  • Games: shuffle a deck of cards, loot options, or turn order.
  • A/B testing and experiments: randomize a list before assigning items to groups, with additional care when fairness or repeatability matters.
  • Data sampling: shuffle records before selecting a subset for a simple randomized workflow.
Take Quiz

Real Codebase Usage

In production code, the choice between mutation and copying is important.

Mutate a local working array

When an array is created specifically for one operation, changing it in place is often simple and efficient.

const deck = createDeck();
shuffleInPlace(deck);
dealCards(deck);

Preserve shared state

If an array comes from application state, a function argument, a cache, or a constant configuration value, create a copy first.

const visibleProducts = shuffled(allProducts);

This is especially common in UI code, where unexpected mutation can make state updates difficult to track.

Inject randomness for tests

Tests often need predictable behavior. A reusable shuffle function can accept a random-number function:

function shuffleInPlace(array, random = Math.random) {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
  return array;
}

A test can provide a controlled random function instead of relying on unpredictable values from Math.random().

Common Mistakes

Using sort() with a random comparator

This common shortcut should be avoided:

const shuffled = arr1.sort(() => Math.random() - 0.5);

Problems:

  • sort() mutates arr1.
  • A sorting comparator is expected to behave consistently; random results violate that expectation.
  • The resulting permutations are not uniformly distributed.
  • Results can vary by JavaScript engine and implementation details.

Use Fisher-Yates instead.

Accidentally changing the original array

This mutates the original array:

const result = shuffleInPlace(arr1);

If arr1 must not change, copy it:

const result = shuffleInPlace([...arr1]);

Choosing the wrong random-index range

This excludes index i:

const j = Math.floor(Math.random() * i);

Use i + 1 so that every index from 0 through i can be selected:

const j = Math.floor(Math.random() * (i + 1));

Using Math.random() for secrets

Do not use this algorithm with Math.random() to generate passwords, secret codes, session IDs, or cryptographic material. Use the Web Crypto API or a platform-approved cryptographic random source for those tasks.

Expecting the order to always visibly change

A shuffle may occasionally return the same order, especially for short arrays. That is a valid random outcome.

Comparisons

ApproachChanges original array?Uniform shuffle?Recommended use
Fisher-Yates in placeYesYes, with a uniform random sourceLocal arrays that can be changed
Fisher-Yates on [...array]NoYes, with a uniform random sourceShared state and reusable input
array.sort(() => Math.random() - 0.5)YesNoAvoid
Selecting random items repeatedlyDependsOften error-proneUse only when sampling is the actual goal

Shuffle versus random selection

A shuffle rearranges all elements. Random selection picks one or more elements.

const oneLetter = arr1[Math.floor(Math.random() * arr1.length)];

The code above selects one random value; it does not shuffle the array.

In-place versus copied shuffle

  • Choose in-place shuffling when mutation is expected and safe.
  • Choose a copied shuffle when callers may still need the original order.
Take Quiz

Cheat Sheet

function shuffleInPlace(array) {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
  return array;
}
// Shuffle the existing array
shuffleInPlace(arr);

// Return a shuffled copy
const copy = shuffleInPlace([...arr]);
  • Loop backward: array.length - 1 down to 1.
  • Pick j from 0 through i: Math.floor(Math.random() * (i + 1)).
  • Swap array[i] and array[j].
  • Empty and one-item arrays work without special handling.
  • Fisher-Yates runs in O(n) time and uses O(1) extra space when shuffling in place.
  • Avoid sort(() => Math.random() - 0.5).
  • Do not use Math.random() for security-sensitive randomness.

FAQ

How do I shuffle an array without changing the original in JavaScript?

Copy it first, then shuffle the copy:

const shuffledArray = shuffleInPlace([...originalArray]);

Does JavaScript have a built-in shuffle() method?

No. JavaScript arrays do not have a built-in standard shuffle() method, so implement Fisher-Yates or use a trusted utility library when one is already part of the project.

Why does Fisher-Yates loop backward?

Each iteration places one random remaining element into its final position. Moving backward ensures the unshuffled portion is always indexes 0 through i.

Can a shuffle return the array in its original order?

Yes. The original order is one valid permutation, so a correct shuffle can occasionally produce it.

Is sort(() => Math.random() - 0.5) a valid shuffle?

No. It is biased, mutates the array, and relies on a comparator that does not follow sorting rules. Use Fisher-Yates.

Does shuffling copy objects inside an array?

No. [...array] creates a new outer array, but object elements remain references to the same objects. Shuffling changes their order, not the objects themselves.

Can I use Math.random() to shuffle passwords or secret tokens?

No. Math.random() is not cryptographically secure. Use a cryptographic random source instead.

Take Quiz

Mini Project

Description

Build a small playlist randomizer. It takes a list of songs, returns a shuffled playlist, and keeps the original library order unchanged. This mirrors common application behavior where source data is preserved while a temporary viewing or playback order is randomized.

Goal

Create a function that returns a shuffled playlist copy and displays both the original and shuffled lists.

Requirements

Use an array containing at least five song titles. Create a Fisher-Yates shuffle function. Do not change the original playlist array. Return and display a shuffled copy. Confirm that both arrays contain the same number of songs.

Take Quiz

Keep learning

Related questions

View more