Random Shuffle
Distribution Analysis
Probability
Statistical Anomalies
Randomness

What distribution do you get from this broken random shuffle?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

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.


Course illustration
Course illustration

All Rights Reserved.