Fisher-Yates Shuffle Algorithm Explained
The Fisher-Yates Shuffle, also known as the Knuth Shuffle, is an algorithm used to randomly shuffle the elements of an array.
It is considered the standard way to generate a random permutation because it ensures that every possible arrangement has an equal probability of occurring.
The algorithm is efficient, simple, and runs in linear time.
Why Not Just Use sort()?
A common mistake is trying to shuffle an array like this:
array.sort(() => Math.random() - 0.5);
While this may appear to work, it does not produce a truly uniform distribution. Some permutations become more likely than others depending on the sorting implementation.
For a fair shuffle, Fisher-Yates is the preferred solution.
How the Algorithm Works
The idea is surprisingly simple:
- Start from the last element in the array.
- Pick a random index between
0and the current position. - Swap the current element with the randomly chosen element.
- Move one position to the left.
- Repeat until the entire array has been processed.
Each element gets exactly one chance to be placed into its final position.
Step-by-Step Example
Let’s shuffle:
[1, 2, 3, 4, 5]
Step 1
Current index: 4
Random index: 1
Swap:
[1, 5, 3, 4, 2]
Step 2
Current index: 3
Random index: 2
Swap:
[1, 5, 4, 3, 2]
Step 3
Current index: 2
Random index: 0
Swap:
[4, 5, 1, 3, 2]
Step 4
Current index: 1
Random index: 1
No change:
[4, 5, 1, 3, 2]
Final shuffled array:
[4, 5, 1, 3, 2]
JavaScript Implementation
function fisherYatesShuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const randomIndex = Math.floor(Math.random() * (i + 1));
[array[i], array[randomIndex]] = [
array[randomIndex],
array[i]
];
}
return array;
}
const numbers = [1, 2, 3, 4, 5];
console.log(fisherYatesShuffle(numbers));
Possible Output
[3, 1, 5, 2, 4]
Because the algorithm is random, your output will likely be different each time.
Immutable Version
If you want to preserve the original array, create a copy before shuffling.
function shuffled(array) {
const copy = [...array];
for (let i = copy.length - 1; i > 0; i--) {
const randomIndex = Math.floor(Math.random() * (i + 1));
[copy[i], copy[randomIndex]] = [
copy[randomIndex],
copy[i]
];
}
return copy;
}
const original = [1, 2, 3, 4, 5];
const shuffledArray = shuffled(original);
console.log(original);
// [1, 2, 3, 4, 5]
console.log(shuffledArray);
// Random order
Why It Works
At each step:
- One position is finalized.
- Every remaining element has an equal chance of being selected.
- No element is favored over another.
This guarantees that all possible permutations are equally likely.
For an array of length n, there are n! possible permutations.
Fisher-Yates gives each of them exactly the same probability.
Time and Space Complexity
| Operation | Complexity |
|---|---|
| Time | O(n) |
| Space | O(1) |
For the immutable version:
| Operation | Complexity |
|---|---|
| Time | O(n) |
| Space | O(n) |
Common Use Cases
- Shuffling a deck of cards
- Randomizing quiz questions
- Randomizing game elements
- Lottery or raffle systems
- Sampling data for testing
Common Pitfalls
Using sort(() => Math.random() - 0.5)
array.sort(() => Math.random() - 0.5);
This approach:
- Produces biased results
- Depends on the JavaScript engine
- Is generally slower than Fisher-Yates
Whenever you need a truly random shuffle, prefer Fisher-Yates.