Guides

How random is a random picker, really?

Computers can't produce randomness by thinking. Here's where the numbers come from, the two bugs that quietly skew nearly every home-made picker, and why a fair draw so often looks rigged.

A computer executing instructions is deterministic by design — same input, same output, every time. That's the property we want from everything except randomness. So where does a random picker get its randomness?

Two different kinds of random

Pseudorandom (PRNG)

An algorithm that takes a starting number — the seed — and grinds out a sequence that passes statistical tests for randomness. It's fully deterministic: same seed, same sequence, forever. That reproducibility is a feature for simulations and game replays, and a catastrophe for anything secret. Math.random() in a browser is this kind.

Cryptographically secure (CSPRNG)

Same idea, but seeded from genuine physical entropy the operating system collects — timing jitter, hardware noise, interrupt patterns — and built so that seeing past outputs tells you nothing about future ones. In a browser this is crypto.getRandomValues(). It's the right choice for anything where predicting the next value would matter: keys, tokens, UUIDs, a prize draw with something at stake.

The difference is not about "how random it looks" — both pass the statistical tests. It's about whether an observer who has watched enough output can predict the next one. For a PRNG, given enough samples, yes.

Random picker → Draws from your list using the browser's cryptographic random source.

Bug one: modulo bias

You want a number from 0 to 9, and your source gives you a byte from 0 to 255. The obvious move:

pick = randomByte % 10

It's subtly unfair. 256 doesn't divide by 10. The values 0–5 each come up 26 times across the byte range; 6–9 come up 25 times. So the low results are about 4% more likely than the high ones — invisible in casual use, and a real bias if it's deciding who wins something.

The fix is rejection sampling: discard values that fall in the incomplete final block and draw again. Here, reject 250–255 and take the modulo only of 0–249, which divides evenly. You throw away about 2% of draws and get an exactly uniform result.

Language built-ins mostly handle this now — JavaScript's Math.random() returns a float rather than an integer, and Python's random.randrange does the rejection for you. Home-rolled pickers using % n on a raw byte usually don't.

Bug two: the shuffle almost everyone gets wrong

The intuitive shuffle:

for each position i: swap item[i] with item[random position]

Looks fine, isn't. It generates nn equally likely execution paths mapped onto n! possible orderings, and since nn isn't divisible by n! for n > 2, some orderings come out more often than others. For a 3-item list the bias is already measurable; for a deck of cards it's severe.

The correct algorithm is Fisher-Yates, and the only difference is the range the swap partner is drawn from:

for i from n−1 down to 1: swap item[i] with item[random 0..i]

Drawing from 0 to i rather than the whole array makes exactly n! outcomes, each once. Every permutation equally likely, provably. One character's difference in the code; the difference between fair and not.

A related trap: sorting with a random comparator (list.sort(() => Math.random() − 0.5)) is not a shuffle. The result depends on the sort algorithm's internals and is reliably lopsided. It's popular because it's short and it looks shuffled.

Spin wheel → Weighted draws with equal-probability segments — the visual version of the same maths.

Why fair randomness looks rigged

People are poor judges of randomness, and consistently in the same direction: we expect it to be more evenly spread than it is.

This is why music streaming services stopped using true random shuffle. Genuinely random playback plays the same artist twice in a row often enough that users reported it as broken. The "shuffle" you get now is deliberately spread out — less random, more like what people mean when they say random.

What to use, when

UseSourceWhy
Game, animation, simulationMath.random()Fast; reproducible from a seed if you want replays
Prize draw, name pickercrypto.getRandomValues()Unpredictable, and defensible if anyone asks
Passwords, tokens, keysCSPRNG, alwaysA predictable secret is not a secret
UUIDscrypto.randomUUID()RFC 9562, correct entropy, no home-rolled version needed
Scientific work needing reproducibilitySeeded PRNG, seed recordedOthers must be able to reproduce your run exactly

The tools on this site that draw or shuffle use the browser's cryptographic source with rejection sampling, and Fisher-Yates where an order is produced. Not because a name picker is security-critical, but because the correct version costs nothing extra and the biased one is impossible to spot by looking at the output.