Toolmingo
Guides6 min read

How to Randomize a List (Fisher-Yates Shuffle Explained)

Learn how to shuffle a list randomly using the Fisher-Yates algorithm. Covers naive vs correct shuffle, bias, PRNG vs CSPRNG, and code examples in JavaScript, Python, Go, and PHP.

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:

  1. 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.
  2. It is implementation-dependent. Different JavaScript engines (V8, SpiderMonkey, JavaScriptCore) use different sort algorithms, so the bias varies across environments.
  3. 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.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools