Why Fisher-Yates Shuffle Matters
A quiz application needs to show the same six questions in a random order. A simulation needs a random ordering of participants. A card game needs a shuffled deck. All three require a permutation: every original item must appear exactly once, and, under the ideal random model, every possible ordering should have the same probability.
“Swap random pairs a few times” sounds plausible, but it does not by itself give a uniform permutation. Fisher–Yates solves the problem by finalizing one position at a time. At each step it selects uniformly from exactly the items that have not yet been placed. The modern in-place version uses one shrinking loop and constant auxiliary storage.
This lesson implements the backward version, traces a reproducible six-element example, proves its uniformity under explicit randomness assumptions, and counts all choice sequences on a tiny input to demonstrate how an incorrect range creates bias. You will also distinguish an ideal uniform shuffle from a deterministic replay produced by a fixed pseudorandom seed.
Fisher–Yates rearranges an array by processing positions from right to left, swapping position $i$ with a uniformly selected position $j$ satisfying $0\le j\le i$. This freezes one uniformly chosen remaining item at each step and produces each permutation of labeled items with probability $1/n!$, provided the bounded choices are independent and uniform. The core array work is linear and uses constant extra space.
Core Concepts and Terminology
The input is an indexed mutable sequence $A$ of length $n$. The output occupies the same sequence and has exactly the same multiset of items. The sample programs use signed $32$-bit integers, but the shuffle does not inspect values or compare keys; it swaps positions, so the same algorithm works for records and references.
To state uniformity precisely, first imagine that every occurrence has its own label, even when two values look equal. There are $n!$ permutations of these labeled occurrences. A uniform shuffle assigns each probability $1/n!$. When values repeat with multiplicities $c_1,\ldots,c_d$, each distinct visible arrangement represents exactly $\prod_t c_t!$ labeled permutations, so it has probability
$$\frac{\prod_{t=1}^{d}c_t!}{n!}.$$
Thus equal values do not need special treatment. They do make position checks harder: attach identity labels when testing the distribution of individual occurrences.
The backward algorithm uses a boundary $i$. Immediately before processing $i$, positions $[0,i]$ remain active and positions $[i+1,n)$ are final. Here $[0,i]$ is inclusive at both ends, while $[i+1,n)$ excludes $n$. The active prefix has $i+1$ positions. Choose $j$ uniformly from that prefix, swap $A[i]$ and $A[j]$, and then decrease $i$. The choice $j=i$ is essential: leaving an item in place is a valid outcome.
| Term | Meaning and role |
|---|---|
| Permutation | A rearrangement preserving each labeled occurrence; $[2,0,1]$ is one permutation of $[0,1,2]$. |
| Uniform shuffle | Each of the $n!$ labeled permutations has probability $1/n!$ under the ideal model. |
| Active prefix | Positions $0$ through $i$, containing the items not yet finalized. |
| Finalized suffix | Positions $i+1$ through $n-1$; later swaps cannot touch them. |
| Bounded random choice | A fresh integer $j\in\{0,\ldots,i\}$, uniform conditional on the entire preceding history. |
| Self-swap | The valid choice $j=i$; the code skips the physical exchange but counts the step. |
| Sampling without replacement | Once an occurrence is placed in the finalized suffix, it cannot be selected again. |
| Seeded replay | Reproducing the same ordering from the same input, generator state, and algorithm; it is not proof of uniformity. |
| Modulo bias | Unequal residue counts when a raw random range is reduced modulo a bound that does not divide its size. |
| Fixed point | An occurrence ending at its original index; a uniform shuffle can have fixed points or even return the original order. |
Uniformity is a property of the whole distribution, not the appearance of a single output. For $n=3$, choosing only among cyclic rotations makes every item equally likely to occupy every position, yet reaches only three of the six permutations. Correct position marginals alone do not prove a uniform shuffle.
How Fisher-Yates Shuffle Works, Step by Step
Use zero-based indices and process $i=n-1,n-2,\ldots,1$. There is no need to process position $0$: once all other positions are fixed, its remaining item is determined.
- Set the current active size to $m=i+1$.
- Generate $j$ uniformly below $m$, giving $0\le j\le i$.
- Swap $A[i]$ and $A[j]$. If their indices are equal, leave the array unchanged.
- Treat position $i$ as final and continue with $i-1$.
The multiset is preserved by every exchange. The finalized suffix grows by one position per iteration, and later choices stay entirely in the active prefix. This supplies both progress and the structure needed for the probability proof.
flowchart TD
A["Set i = array length - 1"] --> B{"i greater than 0?"}
B -->|No| H["Return shuffled array and statistics"]
B -->|Yes| C["Draw j uniformly below i + 1"]
C --> D{"j equals i?"}
D -->|Yes| F["Count the step; position i is final"]
D -->|No| E["Swap A[i] with A[j]; count swap"]
E --> F
F --> G["Decrease i"]
G --> B
Empty and singleton inputs require no draws and no swaps. The algorithm does not test whether the values are already ordered, whether they repeat, or whether a selected pair has equal values. A swap of distinct positions counts even if the visible values match.
To make the three implementations replay identically, they share a specified generator rather than unrelated language-library generators. Its state update is
$$s\leftarrow(16807s)\bmod2147483647,\qquad r=s-1,$$
with seed $1\le s\le2147483646$ and raw range $0\le r\lt B$, where $B=2147483646$. State multiplication uses $64$-bit arithmetic in C++ and Java. This generator is educational and deterministic; the uniformity proof assumes ideal choices, not that this fixed-seed generator supplies independent randomness.
For a bound $m$, set $L=B-(B\bmod m)$. Reject raw draws with $r\ge L$; otherwise return $r\bmod m$. The accepted interval contains exactly $L/m$ representatives of each residue. Consequently, this conversion avoids modulo bias when raw draws are independent and uniform. Rejection cannot remove correlations or add entropy to a deterministic generator.
For example, a raw range $\{0,\ldots,7\}$ reduced modulo $3$ gives residue counts $3,3,2$. Rejecting $6$ and $7$ leaves two representatives for every residue, so each accepted result has probability $1/3$. Do not replace the bounded-integer primitive by rounding a floating-point random value; endpoint and finite-precision rules can introduce other biases.
Worked Example with a Full Trace
Start with $A=[0,1,2,3,4,5]$ and seed $6$. There are five steps. The table records every state update, the corresponding raw value, the chosen index, and the complete array after the exchange. The active sizes are $6,5,4,3,2$; all five raw values fall below their respective acceptance limits, so no rejection occurs in this run.
| Step | $i$ and bound $m$ | New generator state $s$ | Raw value $r=s-1$ | $j=r\bmod m$ | Array after step | Finalized suffix |
|---|---|---|---|---|---|---|
| $1$ | $i=5$, $m=6$ | $100842$ | $100841$ | $5$ | $[0,1,2,3,4,5]$ | $[5]$ |
| $2$ | $i=4$, $m=5$ | $1694851494$ | $1694851493$ | $3$ | $[0,1,2,4,3,5]$ | $[3,5]$ |
| $3$ | $i=3$, $m=4$ | $1145965850$ | $1145965849$ | $1$ | $[0,4,2,1,3,5]$ | $[1,3,5]$ |
| $4$ | $i=2$, $m=3$ | $1614694654$ | $1614694653$ | $0$ | $[2,4,0,1,3,5]$ | $[0,1,3,5]$ |
| $5$ | $i=1$, $m=2$ | $422202639$ | $422202638$ | $0$ | $[4,2,0,1,3,5]$ | $[2,0,1,3,5]$ |
At step $1$, $j=i=5$. The self-choice is accepted, position $5$ becomes final, and no physical swap is counted. At step $2$, indices $4$ and $3$ exchange, placing value $3$ in final position $4$. At step $3$, only indices $0$ through $3$ are eligible, so swapping $3$ with $1$ cannot disturb the suffix $[3,5]$.
The final result is $[4,2,0,1,3,5]$, with $5$ steps, $4$ exchanges of distinct positions, and $5$ raw draws. Position $0$ is determined after the loop. The remaining fixed point at position $5$ is valid: a correct shuffle is not required to move every item.
Under ideal independent uniform choices, this particular sequence of indices has probability
$$\frac16\cdot\frac15\cdot\frac14\cdot\frac13\cdot\frac12=\frac1{720}.$$
For labeled inputs, each final permutation corresponds to exactly one such choice sequence, so the displayed ordering has that same probability in the ideal model. With the fixed seed $6$, the program deterministically produces it; a deterministic replay does not itself have a $1/720$ success probability.
Trace It by Hand
Pause after step $2$, with array $[0,1,2,4,3,5]$, boundary $i=3$, and finalized suffix $[3,5]$. The next choice is $j=1$. Predict the new array and the new suffix before reading on. Swapping indices $3$ and $1$ gives $[0,4,2,1,3,5]$, and the suffix becomes $[1,3,5]$.
Now start from $[A,B,C]$ and enumerate the choices: $j_2$ has three possibilities and $j_1$ has two. You should obtain each of $ABC,ACB,BAC,BCA,CAB,CBA$ exactly once across the six schedules. Finally, remove the self-choice by forcing $j\lt i$; only two schedules remain, so this modified algorithm cannot be a uniform shuffle over all six permutations.
Implementation in Python, C++, and Java
Every panel contains a complete program. fisher_yates or fisherYates modifies its array in place and receives a generator object. Keeping that object outside the routine makes it possible to continue the same random stream across several shuffles. A fresh generator with the same seed, applied to a fresh copy of the same original array, reproduces the same result.
The portable contract accepts arrays of signed $32$-bit integers with length at most $2147483646$. Seeds must lie in $1$ through $2147483646$. The Python implementation can also swap other values, but its cross-language demonstration uses the same integer contract. Empty and singleton arrays preserve the generator state and report zero steps, swaps, and draws.
steps counts completed loop iterations, including self-choices. swaps counts exchanges of two different positions. draws is the number of generator state updates used by this call, including rejected values; it is measured relative to the generator's starting draw count, so reusing a generator does not mix the statistics of different calls. The final state is printed to make replay independently checkable.
Run Python 3 directly. Compile C++ with g++ -std=c++14 -O2 main.cpp -o shuffle, or compile Java 17 or later with javac Main.java and run java Main. Each program prints exactly these five lines:
shuffled = [4, 2, 0, 1, 3, 5]steps=5, swaps=4, draws=5final state = 422202639multiset check = PASSreplay check = PASS
The multiset check uses sorted copies, and the replay check starts from a fresh copy of the original input. These are useful integrity checks; neither proves uniformity. Do not compare a shuffled array with a sorted array expecting equality, and do not expect a second call on an already-shuffled array to reproduce the first result merely because the seed was reset.
The sample's sorting-based verification takes $O(n\log n)$ time and its copies take $O(n)$ storage. Those costs belong to the demonstration harness, not to the core shuffle. The shuffle itself performs no key comparisons and does not allocate an auxiliary array.
Complexity, Correctness, and Invariants
For $n\ge2$, the algorithm performs exactly $n-1$ bounded choices and exactly $n-1$ loop steps, independent of the data or the choices. It performs at most $n-1$ physical swaps, with a minimum of zero if every draw is a self-choice. Thus its array work is $\Theta(n)$ in the best, average, and worst case, assuming a constant-cost bounded-random primitive. It uses $O(1)$ auxiliary space with no recursion. Empty and singleton cases take constant time.
Actual random-range conversion may retry. If $D$ raw draws are used, these implementations take $\Theta(n+D)$ time. Under independent uniform raw draws, the rejection sampler accepts with probability greater than $1/2$, so expected $D$ is less than $2(n-1)$ for $n\ge2$ and expected total time remains $\Theta(n)$. An ideal rejection stream has no finite deterministic upper bound on its number of retries; the fixed-seed finite generator has a different deterministic behavior. Do not describe its variable raw draw count as exactly $n-1$ in every run.
The random information required for an ideal uniform labeled permutation is $\log_2(n!)=\Theta(n\log n)$ bits. This is compatible with linear word-level work when random integers and array operations are treated as word operations. A deterministic generator does not create new entropy at every state update.
| Method | Work and storage | Distribution or limitation |
|---|---|---|
| Backward Fisher–Yates | Exactly $n-1$ steps for $n\ge2$; $\Theta(n)$ array work and $O(1)$ space | Uniform with independent uniform choices from $[0,i]$. |
| Forward Fisher–Yates | $\Theta(n)$ array work and $O(1)$ space | Choose $j$ from $[i,n-1]$ and finalize a prefix; equally valid with the matching invariant. |
| Inside-out construction | $\Theta(n)$ work and $\Theta(n)$ output storage | Builds a new random permutation as input arrives; preserves the input sequence. |
| Repeated random removal from a flat array | Typically $\Theta(n^2)$ due to shifts; $\Theta(n)$ output storage | Uniform if each remaining occurrence is selected uniformly; much more copying than swaps. |
| Sort by independent continuous random keys | Typically $O(n\log n)$ work and linear key storage | Uniform if all keys are independent, identically distributed, and almost surely distinct; finite key ties need separate analysis. |
| Swap every position with a full-range random index | $\Theta(n)$ work and $O(1)$ space | Generally biased; linear time and random-looking output are insufficient. |
| Partial backward Fisher–Yates for $k$ items | Exactly $k$ steps and $\Theta(k+1)$ time for $0\le k\lt n$ on an existing array; $O(1)$ extra state | Finalizes a uniform ordered suffix sample without replacement; a full shuffle can omit the last forced choice. |
| Reservoir sampling of size $k$ from a stream | $\Theta(n)$ work and $O(k)$ storage | Maintains a uniform subset of occurrences; a standard reservoir's internal order need not be uniformly shuffled. |
The deterministic invariant is simple: the array always contains exactly the original occurrences, and the finalized suffix is never changed again. It holds because each step swaps only positions at most $i$. The boundary decreases on every iteration, so the loop terminates.
For the probabilistic invariant, assume every choice is uniform conditional on the entire preceding history. Before processing $i$, the finalized suffix is a uniform ordered selection of $n-i-1$ distinct labeled occurrences. Each remaining occurrence occupies exactly one of the $i+1$ active positions. Choosing $j$ uniformly selects each remaining occurrence with probability $1/(i+1)$, extending the suffix by one uniformly chosen unused occurrence. No assumption that the active prefix is already randomly ordered is needed.
Fix any target labeled permutation $\pi$. The final position must receive $\pi_{n-1}$, then the next position must receive $\pi_{n-2}$ from the remaining occurrences, and so on. Therefore
$$\Pr(A_{\mathrm{final}}=\pi)=\frac1n\cdot\frac1{n-1}\cdots\frac12\cdot1=\frac1{n!}.$$
Equivalently, the numbers of choices are $n,n-1,\ldots,2$, giving $n!$ equally likely schedules, and there is a bijection between schedules and labeled output permutations. Given a target permutation, the desired occurrence for position $i$ identifies exactly one eligible index at that step. Merely counting $n!$ schedules would not suffice without this one-to-one correspondence.
Before each step, the suffix is final and the active prefix contains exactly the unplaced occurrences. A uniform choice from the active prefix places each remaining occurrence at the next final position with equal conditional probability. Induction gives probability $1/n!$ for every labeled output, while the swap invariant independently guarantees that no item is lost or duplicated.
Self-choices are a normal part of this proof. At step $i$, their probability is $1/(i+1)$. For $n\ge1$, the expected number of skipped self-exchanges is $H_n-1$, where $H_n=\sum_{t=1}^{n}1/t$. Hence the expected number of distinct-position swaps is $n-H_n$ under the ideal model. This is a different quantity from the number of final fixed points: an item can move several times and later return to its original position.
For a uniform permutation with $n\ge1$, each labeled occurrence has probability $1/n$ of being fixed, so the expected total number of fixed points is $1$. The original ordering itself occurs with probability $1/n!$; rejecting it and reshuffling changes the target distribution. For $n=0$, there is one empty permutation, with probability $1$, consistent with $0!=1$.
Variants, Pitfalls, and Optimizations
The key implementation decision is the shrinking index range. Consider a buggy backward version on $[0,1,2]$ that processes $i=2$ and $i=1$ but chooses $j$ from all three positions both times. Its nine equally likely schedules cannot split evenly among six permutations. In exact enumeration, outputs $012,021,201$ occur twice each, while $102,120,210$ occur once each. Correct Fisher–Yates has six schedules and produces every permutation exactly once.
Other mistakes and useful variants deserve separate treatment:
- Include the self-choice. Drawing below $i$ instead of below $i+1$ changes the algorithm to Sattolo's cyclic-permutation construction. For $n\gt1$, it produces one-cycle permutations rather than all permutations. Although such permutations have no fixed points, this is not uniform sampling from all derangements either.
- Match direction to the active region. Backward Fisher–Yates chooses from $[0,i]$ and freezes a suffix. A forward version chooses from $[i,n-1]$ and freezes a prefix. Changing the loop direction without changing the sampling interval can destroy the proof.
- Sample a subset without fully shuffling. Performing only the first $k$ backward steps selects a uniform ordered sample in the last $k$ positions. Read in the actual selection order, from the end backward, or reverse that order consistently; both give a uniform ordered sample. For all $n$ items the last remaining item is forced, so the final singleton draw can be omitted.
- Use inside-out construction for a separate output. When inserting input item number $i$, choose $j$ uniformly from $[0,i]$. If $j\ne i$, move the old output at $j$ to the new position $i$; then write the new item at $j$. The condition avoids reading the new slot before it exists. This method needs growing output storage.
- Treat finite-key sorting carefully. Assigning random keys and sorting is slower, and tied keys resolved by original order generally bias the result. A random comparator is worse: it can violate the sorting algorithm's ordering requirements and is not a shuffle algorithm.
- Manage generator state explicitly. Recreating a generator with the same seed before every choice repeatedly uses the first raw value. Sharing mutable state across concurrent tasks can also make replay depend on scheduling; use a documented stream-management strategy.
There is a sharp distinction between an exact mathematical shuffle and a finite seeded implementation. This program has only $B=2147483646$ possible starting states. For a fixed initial array, each state determines one complete output, so at most $B$ distinct permutations can be reached by varying the seed. Already $13!=6227020800\gt B$, making full coverage impossible for thirteen distinct items with this generator. Having fewer permutations than states would still not prove equal probabilities or independence.
Choose an appropriate random source for the application. A simple generator is convenient for teaching, replay, and some controlled experiments; unpredictable dealing or security-sensitive randomization needs a suitable cryptographic source and a correct bounded-integer API. The same Fisher–Yates swap logic can be used with a different source. Its sampling interface and state policy are part of the algorithm's contract.
Choosing from the whole array, excluding $i$, or using a biased bounded-integer conversion changes the distribution. Accept self-choices and fixed points, preserve the shrinking active region, and test exact small cases rather than trusting appearance. A fixed seed guarantees replay of a specified generator and implementation; it does not guarantee ideal uniform permutations or unpredictability.
Practice Problems and Real-World Use
Use three complementary kinds of validation. First, check length and multiplicities after every shuffle, including repeated values and extreme integers. Second, enumerate all legal choice schedules for small labeled arrays using a scripted choice source; each permutation must occur once. Third, test the random-range conversion separately, including rejected raw values. Seed replay checks portability, while statistical experiments are diagnostics rather than proofs.
For larger experiments, count complete permutations when the state space is small enough, and examine pairwise order or joint-position frequencies as well as single-position counts. A statistical test can fail to detect a biased implementation, and a correct random process can occasionally produce an unusual sample. The proof supplies the distributional guarantee under its assumptions; tests check that the implementation follows the specified operations.
| Problem or system | Idea to implement or verify | Difficulty |
|---|---|---|
| Reproduce the six-element example | Match every choice and state, then verify $5$ steps, $4$ swaps, and $5$ raw draws. | Beginner |
| Three-item probability tree | Enumerate the six legal schedules and verify a one-to-one mapping to permutations. | Beginner |
| Biased-shuffle detector | Enumerate the full-range bug's nine schedules and recover probabilities $2/9$ or $1/9$. | Intermediate |
| Quiz or playlist ordering | Preserve occurrences, accept fixed points, and distinguish a uniform order from a deliberately constrained user experience. | Intermediate |
| Simulation or randomized experiment | Record generator state and input order; separate reproducibility from statistical quality. | Intermediate |
| Sample $k$ records without replacement | Stop after $k$ backward steps, verify equal subset probabilities, and specify whether output order matters. | Intermediate |
| Stream permutation construction | Implement inside-out shuffling and verify the distribution while retaining a separate output. | Advanced |
| Generator and entropy audit | Compare seed-space size with $n!$, test bounded conversion, and select a source suited to the application's requirements. | Advanced |
The NIST Dictionary of Algorithms and Data Structures entry describes the shuffle and the limitations of finite pseudorandom generators. The Python random-module documentation documents in-place shuffling, sampling without replacement, state management, and its generator's limitations. Use the proof's conditional-uniformity assumption when assessing an alternative random source, rather than treating any function named “random” as interchangeable.