ToolsAre.Us — Next-Gen Tools Hub ToolsAre.Us — Next-Gen Tools Hub

What “random” means for a computer

Two generators, one classic bias, and the shuffle almost everyone gets wrong.

Last updated 2 September 2026 · ToolsAre.Us Guides

Computers are deterministic machines, which makes producing genuine randomness a real problem rather than a trivial one. The solutions are good enough that the difficulty is invisible most of the time — and then it surfaces, usually as a draw that is subtly unfair or a password that turns out to be predictable.

Two kinds of generator, for two different jobs

Nearly every environment offers both, and using the wrong one is the root of most problems here.

Pseudo-random generators

A PRNG is an arithmetic formula. It holds an internal state, and each request transforms that state and returns a number derived from it. Given the same starting state — the seed — it produces exactly the same sequence, every time, forever.

The output passes statistical tests for randomness: uniform distribution, no obvious correlation, long period before repeating. It is fast, and it is entirely deterministic. In JavaScript this is Math.random().

Determinism is often a feature. A game that can replay a level, a simulation that must be reproducible, a test suite that needs the same "random" data on every run — all of these want a seedable PRNG.

Cryptographically secure generators

A CSPRNG adds one requirement: observing output must not let you predict further output, nor reconstruct what came before. It is seeded from genuine physical entropy the operating system collects — timing jitter, hardware noise, interrupt patterns, dedicated CPU instructions — and built so its internal state cannot be inferred from what it emits.

In a browser this is crypto.getRandomValues(). It is slower and cannot be seeded reproducibly, and both of those are deliberate.

Which to use

The rule is short. If knowing the next value would let someone gain an advantage, you need the cryptographic one. Passwords, tokens, session identifiers, encryption keys, password-reset links, anything with money or fairness attached — CSPRNG, always. Visual effects, procedural placement, sampling, simulations, shuffling a playlist — a PRNG is fine.

Math.random() is not merely "less secure"; in several engines its output has been shown to be reconstructible from a modest number of observed values. A password generator built on it produces passwords that look random and are not. This is the single most consequential mistake in this whole subject, and it is invisible from the output.

Modulo bias: the classic mistake

Suppose you have a generator producing whole numbers from 0 to 255 and you want a number from 1 to 10. The obvious approach:

value = (raw % 10) + 1

This is subtly wrong, and the reason is pure arithmetic. There are 256 possible raw values. Dividing 256 by 10 gives 25 remainder 6. So remainders 0 through 5 each arise from 26 raw values, while remainders 6 through 9 arise from only 25. Outcomes 1 to 6 are about 4% more likely than 7 to 10.

Four percent sounds negligible. In a raffle with a valuable prize, or a card game, or anything a regulator might inspect, it is not — and it is entirely avoidable.

The fix is rejection sampling: discard the raw values that would cause the imbalance and draw again. With 256 values and 10 outcomes, accept 0–249 and reject 250–255. Now each outcome has exactly 25 sources and the distribution is perfectly uniform. The occasional extra draw costs nothing measurable.

The same trap appears in floating-point form. Scaling a fractional random into a range and rounding introduces the same uneven bucket sizes, plus edge cases at the boundaries where the highest value is unreachable or reachable half as often. Whenever a uniform integer genuinely matters, use a range function built for the purpose rather than arithmetic on a fraction.

Shuffling: the one almost everyone gets wrong

The tempting way to shuffle a list is to sort it with a comparator that returns a random result:

items.sort(() => Math.random() - 0.5)   // wrong

It looks shuffled. It is not uniform, and it is worse than merely imperfect.

Sorting algorithms assume a consistent comparator — if A comes before B, then B comes after A, and the ordering is transitive. A random comparator satisfies neither. The algorithm makes decisions based on contradictory answers, so which permutations come out, and how often, depends on the specific sort implementation and even on the input size. Empirically, elements tend to stay near where they started, and some orderings are dramatically more likely than others. The bias is large enough to see in a few thousand trials.

The correct algorithm is Fisher-Yates, and it is shorter than the wrong one:

for i from last index down to 1:
    j = uniform random integer in [0, i]     // inclusive
    swap items[i] and items[j]

Walk backwards; at each step pick a random position from the unshuffled portion including the current one and swap. Every one of the possible orderings is equally likely, and it touches each element once.

Two details matter. The range must include the current index — excluding it is a well-known variant that produces a provably non-uniform result, and it looks fine casually. And the random integer must itself be unbiased, or you have simply moved the modulo problem inside a correct algorithm.

Making a draw genuinely fair

Statistical fairness and demonstrable fairness are different problems. A raffle can be perfectly implemented and still be disbelieved, because the participants cannot see inside it.

The general technique is commit-and-reveal. Before the draw, publish a cryptographic hash of your random seed. The hash reveals nothing about the seed but fixes it — you cannot change it later without the published hash failing to match. After the draw, publish the seed itself. Anyone can now hash it to confirm it is the value you committed to, then re-run the documented selection algorithm and verify the winner.

Some draws strengthen this by combining a committed organiser seed with a public value nobody could have predicted at commit time — a future lottery result, a stock index close, a specified blockchain block hash. Neither party can then steer the outcome alone.

Simpler measures help even without any of that: state the entrant list before drawing, describe the algorithm, and keep the number of entries fixed and public. Most disputes are about opacity rather than arithmetic.

Why random results rarely look random

A genuinely random sequence contains clumps, streaks and repetitions, and human intuition rejects them.

Shuffle a playlist of 100 tracks and the same artist will often appear twice in a row. That is expected: with enough songs, runs are likely, and their absence would be the suspicious result. Several music services eventually replaced true shuffles with deliberately less random algorithms that spread artists out, because users complained the real thing was broken.

Two related errors are worth naming. The gambler's fallacy is believing a run of one outcome makes the other "due" — independent events have no memory, and a coin that has landed heads ten times has exactly even odds on the eleventh. The birthday paradox runs the other way: collisions are far likelier than intuition suggests. In a room of 23 people the odds that two share a birthday exceed half. The practical lesson is that short random identifiers collide far sooner than expected, which is why identifiers meant to be unique are long.

Practical rules

← All guides