How Random Is Random?
Math.random fails the first β it is a fast deterministic algorithm, not an unpredictable
one. A naive % 6 fails the second, because 256 does not divide evenly by six. Use
crypto.getRandomValues with rejection sampling and both problems disappear.Two different jobs called “random”
Software has two kinds of random number generator, and they are built for opposite purposes.
A PRNG β pseudo-random number generator β is an algorithm that turns a small internal state into
a long sequence of well-spread numbers. It is deterministic: the same starting state always gives the
same sequence. That is a feature for games and simulations, where you often want to reproduce a run
exactly, and it makes the generator very fast. Math.random is one of these.
A CSPRNG β the C standing for cryptographically secure β is built so that an observer who has
seen a great deal of the output still cannot predict the next value or reconstruct the previous ones.
It is seeded from operating-system entropy: timing jitter, hardware sources, whatever the platform
collects. crypto.getRandomValues is the browser’s interface to it, and it is the same machinery
that generates encryption keys.
For a spinning wheel deciding who does the washing up, the difference is academic. For a draw where somebody wins something, it is the difference between a generator designed to be unpredictable and one that merely looks it. Since the secure version costs essentially nothing, there is no reason to use the other.
The bug that hides in one character
Getting an unpredictable source is the easy half. The half that catches people out is fitting it to your range.
Suppose you want a number from 0 to 5 β a die. You take a random byte, which is a value from 0 to
255, and you write byte % 6. It looks obviously correct. It is not.
256 divided by 6 is 42 remainder 4. So the byte values map onto the six outcomes like this:
| Result | Byte values that produce it | Count | Probability |
|---|---|---|---|
| 0 | 0, 6, 12 β¦ 252 | 43 | 16.80% |
| 1 | 1, 7, 13 β¦ 253 | 43 | 16.80% |
| 2 | 2, 8, 14 β¦ 254 | 43 | 16.80% |
| 3 | 3, 9, 15 β¦ 255 | 43 | 16.80% |
| 4 | 4, 10, 16 β¦ 250 | 42 | 16.41% |
| 5 | 5, 11, 17 β¦ 251 | 42 | 16.41% |
A uniform die would give 16.67% to every face. Here, four faces are about 2.4% more likely than the other two. In ten rolls you would never see it. In a million rolls it is unmistakable, and in a draw with real entries it means the people at the top of your list are quietly favoured.
The fix is rejection sampling, and it is almost embarrassingly simple: throw the awkward values away. The largest multiple of 6 that fits inside 256 is 252, so accept bytes 0 to 251 and redraw whenever you get 252, 253, 254 or 255. That discards 4 values in 256 β about 1.6% of draws β and what remains divides perfectly. Every tool on this site does this, which is why the random number generator can claim a genuinely flat distribution across any range you set.
Shuffling is where good intentions go wrong
The same trap has a shuffling version, and it is even more common.
The tempting one-liner is to sort the list with a comparator that returns a random result. It reads beautifully and it is wrong: sort algorithms assume a consistent ordering, and feeding them a random one produces a distribution that favours certain arrangements. Worse, the output looks completely shuffled, so the bug survives review indefinitely.
FisherβYates is the correct answer and has been since 1938, when Ronald Fisher and Frank Yates described it for shuffling by hand. Richard Durstenfeld gave it the modern in-place form in 1964, and Knuth documented it in The Art of Computer Programming, which is why it also travels under his name. The procedure: walk backwards through the list, and at each position swap the item with one chosen uniformly from the positions at or before it.
One pass. Every one of the n! possible orderings equally likely β six items give 720 orderings, ten give 3,628,800, and each is drawn evenly. It is what the list randomizer runs, what deals the team generator, what shuffles the deck behind random card, and what builds the Secret Santa chain.
What none of this can tell you
Even a perfect generator produces sequences that look wrong to human eyes. Streaks are the classic case: five heads in a row happens about one time in 32, and a hundred flips without a single run of five would be the genuinely suspicious result. People asked to write down a “random” sequence produce far fewer repeats than chance does, which is exactly how fabricated data gets caught.
So the honest summary is that randomness cannot be verified by looking at it. It can only be verified by understanding where it came from β which is why the methodology page exists, and why is a coin flip really 50/50 is a story about physics rather than about a coin.
Frequently asked questions
Is Math.random actually random?
It is deterministic. Browsers implement it with a fast non-cryptographic algorithm that produces a long, evenly distributed sequence from an internal state β good enough for animations and games, and never intended to be unpredictable. Given enough consecutive outputs, the state can in principle be recovered and future values predicted, which is precisely what you do not want in a draw.
What is a CSPRNG?
A cryptographically secure pseudo-random number generator: one seeded from operating-system entropy and built so that seeing past output tells you nothing useful about future output. crypto.getRandomValues is the browser interface to it, and it is the same source used to generate encryption keys. It is also fast enough that there is no reason to prefer the weaker option.
What exactly is modulo bias?
The skew you get when your range does not divide evenly into the pool of random values. A byte holds 256 values; 256 divided by 6 is 42 remainder 4, so remainders 0 to 3 occur 43 times each and remainders 4 and 5 occur only 42. Low faces come up about 2.4% more often β invisible in ten rolls, obvious in a million.
How does rejection sampling fix it?
By throwing away the leftovers. For a six-sided range, accept only the byte values 0 to 251, since 252 is the largest multiple of 6 that fits, and redraw whenever a byte lands on 252 to 255. That discards about 1.6% of draws and makes the remaining ones exactly uniform. The wasted bytes are the entire cost of correctness.
Why is FisherβYates the right shuffle?
Because it is provably uniform: after one backwards pass, every possible ordering of the list is exactly as likely as every other. The popular alternative β sorting with a random comparator β produces a measurably lopsided distribution that gets worse as the list grows, and it looks perfectly shuffled while doing so. That is what makes it dangerous.
How the randomness works
Sources: