Random Shuffle
Distribution Analysis
Probability
Statistical Anomalies
Randomness

What distribution do you get from this broken random shuffle?

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

A correct random shuffle produces every permutation of a dataset with equal probability. But what happens when the shuffle algorithm has a bug? This article examines the most common "broken" shuffle, analyzes the non-uniform distribution it produces, and explains why the Fisher-Yates algorithm is the correct approach.

What Makes a Shuffle Correct?

For a dataset of nn elements, there are n!n! possible permutations. A correct shuffle gives each permutation exactly a 1n!\frac{1}{n!} probability of occurring. The Fisher-Yates (Knuth) shuffle achieves this by iterating from the end of the array to the beginning, swapping each element with a randomly chosen element from the remaining unprocessed portion.

python
1import random
2
3def fisher_yates_shuffle(arr):
4    for i in range(len(arr) - 1, 0, -1):
5        j = random.randint(0, i)
6        arr[i], arr[j] = arr[j], arr[i]

This algorithm makes exactly n1n - 1 swaps and produces each of the n!n! permutations with equal probability.

The Classic Broken Shuffle

The most common broken shuffle swaps each element with a random position chosen from the entire array, not just the unprocessed portion:

python
1def broken_shuffle(arr):
2    n = len(arr)
3    for i in range(n):
4        j = random.randint(0, n - 1)  # BUG: should be randint(i, n-1)
5        arr[i], arr[j] = arr[j], arr[i]

The subtle difference is that j ranges over all nn positions at every step, rather than only the positions from ii to n1n-1.

Why This Produces a Biased Distribution

The broken shuffle makes nn independent random choices, each from nn options, producing nnn^n equally likely execution paths. The key problem is that nnn^n is generally not divisible by n!n!. For example, with n=3n = 3:

  • Number of execution paths: 33=273^3 = 27
  • Number of permutations: 3!=63! = 6
  • 27/6=4.527 / 6 = 4.5, which is not an integer

Since 27 execution paths cannot be evenly distributed across 6 permutations, some permutations must be more likely than others.

Empirical Analysis for n = 3

Running the broken shuffle on [A, B, C] one million times produces results like:

PermutationObserved FrequencyExpected (Uniform)
[A, B, C]14.8%16.67%
[A, C, B]11.1%16.67%
[B, A, C]14.8%16.67%
[B, C, A]18.5%16.67%
[C, A, B]22.2%16.67%
[C, B, A]18.5%16.67%

The distribution is clearly non-uniform. The permutation [C, A, B] appears about 33% more often than expected, while [A, C, B] appears about 33% less often.

Exact Probabilities

For the 3-element case, we can enumerate all 27 execution paths:

PermutationPaths leading to itProbability
[A, B, C]44/274/27
[A, C, B]33/273/27
[B, A, C]44/274/27
[B, C, A]55/275/27
[C, A, B]66/276/27
[C, B, A]55/275/27

Statistical Detection

Chi-Squared Goodness-of-Fit Test

To statistically verify that a shuffle is biased, use the chi-squared test:

χ2=i=1k(OiEi)2Ei\chi^2 = \sum_{i=1}^{k} \frac{(O_i - E_i)^2}{E_i}

where OiO_i is the observed count of permutation ii, EiE_i is the expected count under a uniform distribution, and k=n!k = n! is the number of permutations.

With enough trials, a broken shuffle will produce a χ2\chi^2 value far exceeding the critical value, confirming non-uniformity with high confidence.

Positional Bias Matrix

Another useful diagnostic is a position-frequency matrix. For each element, count how often it lands in each position across many trials. In a correct shuffle, each element should appear in each position with probability 1n\frac{1}{n}. The broken shuffle will show systematic biases, for example later elements tending to cluster toward the front.

Why Does This Matter?

Biased shuffling has real consequences in several domains:

  • Online games: Card shuffles or matchmaking that favor certain outcomes create unfair play.
  • A/B testing: Biased assignment of users to test groups invalidates experimental results.
  • Cryptography: Non-uniform randomness weakens security protocols.
  • Simulations: Monte Carlo simulations relying on uniform random permutations will produce incorrect results.

The Fix: Fisher-Yates

The Fisher-Yates algorithm avoids the bias by progressively narrowing the range from which the swap target is chosen:

python
1def correct_shuffle(arr):
2    for i in range(len(arr) - 1, 0, -1):
3        j = random.randint(0, i)  # j in [0, i], not [0, n-1]
4        arr[i], arr[j] = arr[j], arr[i]

This produces exactly n×(n1)××1=n!n \times (n-1) \times \ldots \times 1 = n! equally likely execution paths, one for each permutation. It runs in O(n)O(n) time and O(1)O(1) extra space.

Summary

The broken shuffle (swapping with a random element from the full array) produces a non-uniform distribution because nnn^n execution paths cannot map evenly onto n!n! permutations. The bias grows with array size. Always use the Fisher-Yates shuffle, which guarantees uniformity by restricting the swap range at each step. When in doubt, verify your shuffle with a chi-squared test on the permutation frequencies.


Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.