Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Coding Interview Patterns  Module 18 – Maths for coding rounds

Random shuffles and sampling

The Fisher–Yates shuffle, reservoir sampling from a stream and weighted random picks, with seeded tests and the shortcuts that bias the result.

  • Intermediate
  • 35 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25.0.4.1, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free intro, Pro: 6 of 35 minutes free
Free: Three draws for a college fest · The brute force, and where it breaks · What to notice. With a pass: the other 12 sections and 3 interview answers, about 29 minutes.

What you will learn

  • Implement a uniform shuffle with Fisher–Yates and prove that every order is equally likely
  • Select k items from a stream of unknown length with reservoir sampling
  • Pick indexes by weight with running totals and binary search, never picking a zero weight
  • Build a uniform 1 to 10 from a uniform 1 to 7 with rejection sampling, and avoid modulo bias
  • Test random code reproducibly with seeds in Python, JavaScript, Java, C++ and Go

Before you start

What you’ll learn · Before you start

What you will learn

  • Implement a uniform shuffle with Fisher–Yates and prove that every order is equally likely
  • Select k items from a stream of unknown length with reservoir sampling
  • Pick indexes by weight with running totals and binary search, never picking a zero weight
  • Build a uniform 1 to 10 from a uniform 1 to 7 with rejection sampling, and avoid modulo bias
  • Test random code reproducibly with seeds in Python, JavaScript, Java, C++ and Go

Before you start

On this page

The Fisher–Yates shuffle puts n items in random order so that each of the n! orders is equally likely: walk from the last position down to the second, and swap the item there with one at a uniformly random position at or before it. Random shuffles, samples and weighted picks come up in interviews because the obvious code is often quietly unfair. Sampling has two more classics: reservoir sampling keeps k items from a stream whose length nobody knows, and a weighted pick returns index i with probability proportional to its weight. Rejection sampling turns one fair generator into another by discarding the draws that would make the result uneven. Two popular shortcuts, sorting with a random comparator and swapping each item with any position at all, give random shuffles that are measurably biased.

Three draws for a college fest

Problem A: shuffle the entries. A college fest’s lucky draw lists n ≤ 10⁶ registration numbers. Using randbelow(m), which returns a uniform integer from 0 to m − 1, put the list in random order in place, so that each of the n! orders has the same chance.

Problem B: sampling a stream. Tickets are scanned at the gate one by one, and nobody knows how many will come. Keep k = 5 of them in memory so that, when the gate closes, every scanned ticket is among the five with the same probability.

Problem C: pick a stall by weight. Stall i holds wᵢ prize tokens, 0 ≤ wᵢ ≤ 10⁹, for n ≤ 10⁵ stalls. Build a picker once, then answer 10⁵ picks, each returning stall i with probability wᵢ / W, where W is the total. With the weights 1, 3, 0 and 6, stall 3 wins 60 % of the picks and stall 2 never wins.

Fisher–Yates shuffling A, B, C, D in three swaps, and the weights 1, 3, 0, 6 drawn as intervals of a line from 0 to 10.Fisher–Yates on four finalistsstartABCDi = 3, j = 1ADCBi = 2, j = 2ADCBi = 1, j = 0DACB4 × 3 × 2 = 24 choices, one for each order.Weights 1, 3, 0, 6 as intervals#0#1#301410u = 0.63: u × 10 = 6.3 falls in #3.#2, of weight 0, has an empty interval at 4.

A shuffle step by step, and a weighted pick as a point on a line

Text description of the diagram

The upper panel shuffles the four finalists A, B, C and D. At i = 3 a random position j from 0 to 3 is chosen, here 1, and B and D swap, so B is final in the last position: A, D, C, B. At i = 2, j is chosen from 0 to 2, here 2, so C stays and is final. At i = 1, j is chosen from 0 to 1, here 0, and A and D swap, giving D, A, C, B. The final positions are shaded. The three choices have 4, 3 and 2 options, 24 combinations in all, one for each of the 24 orders.

The lower panel draws the weights 1, 3, 0 and 6 as intervals of a line from 0 to 10, the total weight: index 0 covers 0 to 1, index 1 covers 1 to 4, index 2 has weight 0 and an empty interval at 4, and index 3 covers 4 to 10. A random u of 0.63 gives the point 6.3, which falls in the interval of index 3. Each index is hit with probability equal to its share of the line.

The brute force, and where it breaks

Problem A can list every order and pick one: 24 orders for four finalists, but 20! has 19 digits and 10⁶! has 5,565,709. The Fisher–Yates shuffle needs only n − 1 random choices instead. Problem B can store every ticket and sample at the end, with memory for the whole stream, while reservoir sampling holds only the k tickets it may return. Problem C can expand every stall into one copy per token, about 5 × 10¹² copies in the run, or scan the running totals for every pick, about 5 × 10⁹ steps for 10⁵ picks among 10⁵ stalls.

Every order, the whole stream and a scan per pick, with the costs Python · brute/brute_force.py
# The obvious approach to each of the three problems, and what it costs at the stated sizes.
import itertools
import math
import random

rng = random.Random(2026)

# Problem A: list every order and pick one of them uniformly.
finalists = ["R101", "R102", "R103", "R104"]
orders = list(itertools.permutations(finalists))
print(f"4 finalists: {len(orders)} orders, one picked: {list(orders[rng.randrange(len(orders))])}")
for n in (10, 20, 1_000_000):
    digits = len(str(math.factorial(n))) if n < 1000 else int(math.lgamma(n + 1) / math.log(10)) + 1
    print(f"{n:,} entries: {n}! has {digits:,} digits")

# Problem B: keep the whole stream, then sample k = 5 at the end.
kept = [f"T{i:07d}" for i in range(200_000)]  # 200,000 scanned tickets held in memory
winners = rng.sample(kept, 5)
print(f"stream of {len(kept):,} tickets stored to pick {len(winners)} winners")

# Problem C: a linear scan of the running totals for every pick.
weights = [rng.randint(0, 10**9) for _ in range(10_000)]
total = sum(weights)
steps = 0
for _ in range(1_000):
    target = rng.random() * total
    running = 0
    for i, w in enumerate(weights):
        steps += 1
        running += w
        if running > target:
            break
print(f"1,000 picks among 10,000 stalls: {steps:,} steps, about {steps // 1000:,} per pick")
print(f"expanding every stall into one copy per unit of weight: {total:,} copies")

Output

4 finalists: 24 orders, one picked: ['R101', 'R103', 'R104', 'R102']
10 entries: 10! has 7 digits
20 entries: 20! has 19 digits
1,000,000 entries: 1000000! has 5,565,709 digits
stream of 200,000 tickets stored to pick 5 winners
1,000 picks among 10,000 stalls: 4,953,653 steps, about 4,953 per pick
expanding every stall into one copy per unit of weight: 4,944,655,828,737 copies

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute_force.py

What to notice

  • An order is uniform when each position, filled from the end, gets a uniform choice among the items not yet placed.
  • A stream item only needs the right chance of entering when it arrives, k / i for the i-th item, without knowing what comes later.
  • The running totals cut [0, W) into one interval per stall, so a uniform point lands in stall i’s interval with probability wᵢ / W, and finding the interval is a binary search.

Fisher–Yates, and why each order has probability 1/n! Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Two off-by-one versions, and what they really produce
    • Uniformity measured: a million shuffles and a random comparator
  • Reservoir sampling, and why each ticket stays with probability k/N
  • Weighted picks with running totals and binary search
  • Rejection sampling: a fair 1 to 10 from a fair 1 to 7
  • Seeds, reproducible tests and the generators of five languages
  • The code in five languages
  • Edge cases and common bugs
  • Variants: Floyd’s sample, a partial shuffle, a weighted reservoir and a point in a disc
  • Exercise: pick stalls by weight
    • Pick stalls by weight with prefix sums
  • A practice set with sample answers
  • Check yourself
  • Interview questions
    • Prove that Fisher-Yates produces every order of n items with probability 1/n!. Then show why the version that swaps each position with a uniformly random position anywhere in the list cannot be uniform, using n = 3.
    • Why is sorting a list with a comparator that returns a random answer not a fair way to shuffle it?
    • Keep k uniformly random items from a stream of unknown length in O(k) memory. Prove that every item is kept with probability k/N at the end, and say what changes when the stream is split across several machines.
    • A weighted picker must answer picks and also change the weight of one item thousands of times between picks. Running totals need O(n) per change. Which structure makes both a pick and a change O(log n), and how exactly does a pick work on it?
  • Key takeaways

7 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 25 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.