Random Numbers Without Bias, Explained

After reading this you will know how to draw fair random integers, why the common random() % range trick quietly favours small numbers, and how to read a stream of draws without fooling yourself about what "random" means.

What a uniform random integer actually is

A uniform random integer generator picks a whole number from a fixed range so that every value has the same chance. If you draw from 1 to 6, each of the six faces should come up with probability 1/6 \approx 0.1667. Nothing is "hot" or "due". A fair die that just landed on 6 nine times in a row still has probability 1/6 of landing on 6 again.

Here is the hook. Roll a fair die 60 times and you expect about 10 of each face, but you will almost never get exactly 10 of each. A spread of 6 to 14 per face is completely normal. People read those wobbles as patterns. They are not patterns. They are the ordinary noise of small samples, and the math below tells you exactly how big that noise should be.

When to use it, and when not

Reach for a plain random integer when every outcome deserves equal odds and you want a value or a set of values: pick a winner from ticket numbers 1 to 5000, roll dice, generate a PIN, sample rows from a spreadsheet, or draw six lottery balls that must not repeat.

Do not use it when the outcomes are not equally likely. If one raffle entry bought ten tickets and another bought one, you want the ten-ticket entry to win ten times as often. That is a weighting problem, not a uniform draw. Do not use raw integers to assign people to teams either, because keeping team sizes equal is a shuffle, not independent picks.

If you find yourself generating a random number and then writing rules like "but if it repeats, try again" or "but weight the first option higher", a different tool fits better. See the related tools section.

The bias in the naive method, and how rejection sampling fixes it

Suppose your random source gives you an integer from 0 to 15 (16 equally likely values) and you want a number from 0 to 9. The tempting one-liner is:

r = R \bmod 10

Here R is the raw value from 0 to 15, and \bmod is the remainder after division. Count how many raw values map to each result. Values 0 and 10 both give 0. Values 1 and 11 both give 1. This continues up to 5 and 15, which both give 5. But values 6, 7, 8, 9 have no partner, so results 6 through 9 come from one raw value each.

So results 0 to 5 each have probability 2/16 = 0.125, while results 6 to 9 each have probability 1/16 = 0.0625. The small numbers are exactly twice as likely. That is real bias, and it never averages out no matter how many draws you take.

Rejection sampling removes it. Find the largest multiple of your range size that fits inside the raw range, then throw away and redraw any raw value at or above it.

\text{limit} = \left\lfloor \frac{N}{n} \right\rfloor \cdot n

N is the number of raw values available (16 here), n is your range size (10), and the brackets mean round down. So \text{limit} = \lfloor 16/10 \rfloor \cdot 10 = 10. You accept raw values 0 to 9 and reject 10 to 15. Every accepted value maps to exactly one result, so every result has probability 1/10 = 0.1000. The cost is that you sometimes redraw. In this example you reject 6 of 16 raw values, so about 37.5% of the time you draw again. In practice the raw range is enormous (32 bits, over 4 billion values), so rejections are vanishingly rare and every value in your range is exactly equally likely.

A worked example: reproducing the demo

Six numbers from 1 to 49, no repeats

The demo draws a lottery-style set. The mechanics for a unique draw are a partial Fisher-Yates shuffle, not six independent picks.

  1. Start with the pool 1 to 49, which holds 49 values.
  2. Pick position 1 uniformly from all 49. Say it lands on 23. Swap 23 to the front.
  3. Pick position 2 uniformly from the remaining 48 values. Say 7. Swap it into place.
  4. Continue until 6 positions are filled. Because each pick draws only from values not yet used, no number can repeat.
  5. The first six values are your draw, for example 7, 12, 23, 31, 38, 44 once sorted.

Why not just draw six independent numbers and redraw on collision? That works, but as the pool shrinks the collision chance grows, and for a full shuffle of 49 into 49 it becomes slow. The partial shuffle does each draw once and is always fair: every 6-number combination out of 49 is equally likely, and there are \binom{49}{6} = 13{,}983{,}816 of them, so any specific ticket has probability about 7.15 \times 10^{-8}.

Reading a stream of draws

The single most useful number for judging fairness is the standard deviation of counts. Roll a fair n-sided die m times. The count for any one face follows a binomial distribution with expected value and standard deviation:

\mu = \frac{m}{n}, \qquad \sigma = \sqrt{m \cdot \frac{1}{n} \cdot \left(1 - \frac{1}{n}\right)}

\mu is the average count you expect for each face, and \sigma measures the typical wobble around it. For 60 rolls of a 6-sided die, \mu = 10 and \sigma = \sqrt{60 \cdot (1/6) \cdot (5/6)} \approx 2.89. So a face landing 7 or 13 times is about one standard deviation off, entirely ordinary. A face landing 20 times is (20 - 10)/2.89 \approx 3.46 deviations out, which is genuinely rare and worth a second look.

A typical run: counts scatter between 7 and 13 even though the die is fair. The gaps are noise of size roughly one sigma (2.89), not evidence of bias.

Watch the sample size shrink the noise

The wobble does not grow with more rolls, it grows slower than the total. The relative spread \sigma / \mu shrinks as you roll more, which is why long runs look flat and short runs look lumpy.

With a fair 6-sided die, expected count per face is rolls divided by 6. At 60 rolls each face averages 10 with sigma 2.89 (relative spread 28.9%). At 600 rolls each face averages 100 with sigma 9.13 (relative spread 9.13%). At 6000 rolls, average 1000, sigma 28.9, relative spread 2.89%. Ten times the rolls cuts the relative spread by about the square root of ten (3.16x).

Common mistakes

Believing in "due" numbers
Each draw is independent. After five heads, the next flip is still 50/50. A number that has not appeared in a lottery for months is no more likely next time. The technical name for the opposite belief is the gambler's fallacy.
Confusing unique draws with independent draws
Independent draws can repeat. If you need distinct values (six lottery balls, five raffle winners from a list), use the no-repeat mode. If you want each draw fresh and repeats allowed (dice, coin flips), use the with-repeat mode. They answer different questions.
Trusting an off-the-shelf Math.random() for anything sensitive
Ordinary language-level random functions are fast but predictable: an attacker who sees enough outputs can reconstruct the internal state and forecast future values. This tool draws from the browser's cryptographic source instead, so the outputs are not predictable from past outputs. Use cryptographic randomness for tokens, passwords, and anything an adversary could profit from guessing.
Reading a small run as biased
Before you accuse a generator of bias, compute \sigma. A face two or three counts off the mean in a 60-roll run is expected, not suspicious.

Do not build fairness on random() % range. It reliably favours small numbers, and the bias survives any amount of averaging. Rejection sampling costs a rare redraw and removes the bias completely.

Related tools

When outcomes are not equally likely, the Weighted Random Picker lets some entries earn better odds while staying fair to the weights you set. To split names into balanced groups instead of drawing values, use the Team & Pair Randomizer, which shuffles rather than samples so group sizes stay even. For competitions, the Tournament Bracket Generator builds single-elimination brackets with byes, seeded or fully random.

Frequently asked questions

Is this random number generator truly random?

It draws from the browser's cryptographic random source and applies unbiased rejection sampling on top. That means every value in your range is exactly equally likely, and the outputs are not predictable from earlier outputs. It is suitable for tokens and passwords as well as games.

Why did I get the same number twice?

In with-repeat mode, repeats are expected. Drawing two numbers from 1 to 6 gives a matching pair with probability 1/6 \approx 16.7\%. If you never want repeats, switch to the unique mode, which uses a partial Fisher-Yates shuffle so a value cannot appear twice.

My six lottery numbers do not "look random". Is the tool broken?

Almost certainly not. Human eyes expect draws to look spread out and alternate, but genuine random draws cluster and streak more than intuition allows. Runs like 3, 4, 5 appearing together are perfectly ordinary.

How many redraws does rejection sampling cause?

Very few. The raw source spans over four billion values, so the rejected slice is a tiny fraction of one range width. For almost any everyday range the chance of even a single redraw is below 0.0001%.

Does anything I generate get sent to a server?

No. Everything runs locally in your browser. Nothing you enter or generate leaves your device.