How to Randomize a List
Randomizing a list — also called shuffling — sounds trivial, but doing it correctly is surprisingly easy to get wrong. A naive implementation produces biased results where some orderings are far more likely than others. This guide explains the right algorithm, how to avoid bias, and how to implement it in four languages.
What Is List Randomization?
List randomization (shuffling) rearranges items in a random order so that every possible permutation has an equal probability of occurring.
Common use cases:
- Draw orders — raffle prizes, jury selection, tournament brackets
- Quiz questions — present questions in a different order each time
- Playlist shuffling — music and video queues
- A/B testing — randomly assign users to groups
- Card games — shuffle a virtual deck
- Load balancing — randomize server lists to distribute requests
The Wrong Way: Sorting by Math.random()
The most common mistake is using sort() with a random comparator:
// WRONG — biased and inconsistent
const items = [1, 2, 3, 4, 5];
items.sort(() => Math.random() - 0.5);
Why this is wrong:
- It is biased. Sort algorithms expect a consistent, transitive comparator. A random comparator violates this contract, causing some permutations to appear more often than others — provably, not just in theory.
- It is implementation-dependent. Different JavaScript engines (V8, SpiderMonkey, JavaScriptCore) use different sort algorithms, so the bias varies across environments.
- The distribution is not uniform. For a 3-element array
[a, b, c], there are 6 possible permutations. The random-sort approach produces those 6 permutations with wildly unequal frequency.
The Right Way: Fisher-Yates Shuffle
The Fisher-Yates shuffle (also called the Knuth shuffle) is the standard algorithm for unbiased random permutation. It runs in O(n) time and produces each of the n! permutations with exactly equal probability.
Algorithm (modern / Durstenfeld variant):
for i from n-1 down to 1:
j = random integer in [0, i]
swap(array[i], array[j])
Step-by-step example with [A, B, C, D]:
| Step | i | j (random) | Array after swap |
|---|---|---|---|
| 1 | 3 | 1 | [A, D, C, B] |
| 2 | 2 | 0 | [C, D, A, B] |
| 3 | 1 | 1 | [C, D, A, B] |
Result: [C, D, A, B] — a uniformly random permutation.
Why it is unbiased: At each step i, the element placed in position i is chosen uniformly from the remaining unplaced elements. No element gets a second chance, so every permutation of n elements has exactly 1/n! probability.
PRNG vs CSPRNG
For most use cases (games, quizzes, playlists), a standard pseudo-random number generator (PRNG) like Math.random() is fine. For security-sensitive shuffles (lottery draws, cryptographic key ordering), use a cryptographically secure PRNG (CSPRNG):
| PRNG | CSPRNG | |
|---|---|---|
| Speed | Fast | Slightly slower |
| Predictable from seed? | Yes | No |
| Use for games/playlists | ✓ | Overkill |
| Use for lotteries/security | ✗ | ✓ |
| JS API | Math.random() |
crypto.getRandomValues() |
| Python | random.shuffle() |
secrets.SystemRandom() |
Code Examples
JavaScript
// Fisher-Yates — O(n), unbiased
function shuffle(array) {
const result = [...array]; // don't mutate the original
for (let i = result.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[result[i], result[j]] = [result[j], result[i]];
}
return result;
}
const items = ["Alice", "Bob", "Carol", "Dave", "Eve"];
console.log(shuffle(items));
// e.g. ["Dave", "Alice", "Eve", "Carol", "Bob"]
// Cryptographically secure version (for lotteries)
function secureShuffle(array) {
const result = [...array];
const randomValues = new Uint32Array(result.length);
crypto.getRandomValues(randomValues);
for (let i = result.length - 1; i > 0; i--) {
const j = randomValues[i] % (i + 1);
[result[i], result[j]] = [result[j], result[i]];
}
return result;
}
Python
import random
import secrets
items = ["Alice", "Bob", "Carol", "Dave", "Eve"]
# Standard shuffle (mutates in place)
shuffled = items.copy()
random.shuffle(shuffled)
print(shuffled) # e.g. ['Carol', 'Alice', 'Eve', 'Bob', 'Dave']
# Return new list (non-mutating)
shuffled = random.sample(items, len(items))
# Cryptographically secure shuffle
def secure_shuffle(lst):
result = lst.copy()
for i in range(len(result) - 1, 0, -1):
j = secrets.randbelow(i + 1)
result[i], result[j] = result[j], result[i]
return result
print(secure_shuffle(items))
Go
package main
import (
"fmt"
"math/rand"
)
func shuffle(items []string) []string {
result := make([]string, len(items))
copy(result, items)
rand.Shuffle(len(result), func(i, j int) {
result[i], result[j] = result[j], result[i]
})
return result
}
func main() {
items := []string{"Alice", "Bob", "Carol", "Dave", "Eve"}
fmt.Println(shuffle(items))
// e.g. [Carol Dave Alice Eve Bob]
}
rand.Shuffle (added in Go 1.10) implements Fisher-Yates internally — prefer it over rolling your own loop.
PHP
<?php
function shuffle_list(array $items): array {
$result = $items;
// PHP's shuffle() uses Fisher-Yates internally
shuffle($result);
return $result;
}
$items = ["Alice", "Bob", "Carol", "Dave", "Eve"];
$shuffled = shuffle_list($items);
print_r($shuffled);
// Manual Fisher-Yates for clarity or seeded randomness
function fisher_yates(array $items): array {
$n = count($items);
for ($i = $n - 1; $i > 0; $i--) {
$j = random_int(0, $i); // cryptographically secure
[$items[$i], $items[$j]] = [$items[$j], $items[$i]];
}
return $items;
}
print_r(fisher_yates($items));
?>
PHP's random_int() is CSPRNG-backed — prefer it over rand() for any shuffle you care about fairness in.
Picking a Random Subset
To pick k random items from a list without replacement, draw a subset instead of a full shuffle:
// JavaScript — pick 3 from 10
function sample(array, k) {
const shuffled = shuffle(array);
return shuffled.slice(0, k);
}
# Python — built-in
winners = random.sample(items, 3)
This is more efficient than shuffling the full list when k << n — use reservoir sampling for very large or streaming inputs.
Quick Reference
| Task | JavaScript | Python | Go | PHP |
|---|---|---|---|---|
| Shuffle in place | (mutates) — use spread copy | random.shuffle(lst) |
rand.Shuffle(...) |
shuffle($arr) |
| Shuffle — new list | shuffle([...arr]) |
random.sample(lst, len(lst)) |
copy + rand.Shuffle |
fisher_yates($arr) |
| Pick k items | .slice(0, k) after shuffle |
random.sample(lst, k) |
shuffle + [:k] |
array_slice(shuffle, 0, k) |
| Secure shuffle | crypto.getRandomValues() |
secrets.randbelow() |
crypto/rand |
random_int() |
| Seed for reproducibility | — (no seed in browser) | random.seed(42) |
rand.New(rand.NewSource(42)) |
srand(42) |
Frequently Asked Questions
Is Fisher-Yates truly unbiased?
Yes — every permutation of n elements has exactly 1/n! probability. The proof: at step i, the algorithm uniformly selects from i+1 remaining positions, so the joint probability of any specific permutation is (1/n) × (1/(n-1)) × … × (1/1) = 1/n!.
Can I shuffle the same list the same way every time?
Yes — seed the PRNG before shuffling. In Python: random.seed(42); random.shuffle(lst). In Go: rand.New(rand.NewSource(42)). Same seed → same shuffle every time. Don't use seeded shuffles for security-sensitive draws.
What's the difference between shuffle and sort-by-random-key?
Sorting by a random key (items.sort(() => Math.random() - 0.5)) is O(n log n) and biased. Fisher-Yates is O(n) and unbiased. Always prefer Fisher-Yates.
How do I shuffle lines in a text file on the command line?
Linux/macOS: shuf file.txt > shuffled.txt. Windows PowerShell: Get-Content file.txt | Get-Random -Count ([int]::MaxValue). For large files, shuf uses reservoir sampling internally.
Does shuffling affect the original array?
In JavaScript and Go, whether the original mutates depends on whether you copy first — the examples above all copy before shuffling. In Python, random.shuffle() mutates in place while random.sample() returns a new list. Always copy if you need the original intact.
What is reservoir sampling?
Reservoir sampling picks k items from a stream of unknown size in a single pass with O(k) memory — no need to store the entire list. Use it when the input is too large to fit in memory or when reading from a stream. Algorithm L (Vitter 1987) is the standard modern variant.