Where the numbers come from
crypto.getRandomValues(), the operating system’s cryptographic random source — not Math.random(), which is a fast pseudo-random generator whose next output can be predicted from a handful of previous ones.
For a game that difference is academic. For a prize draw, a randomised trial, or anything where someone might benefit from guessing the next value, it is the whole question.
Modulo bias, and why it is avoided here
The obvious way to get a number in a range is to take a random value and use the remainder. Unless the range divides the source evenly, that makes the lower part of the range slightly more likely — a small bias, invisible in a handful of draws and measurable over thousands.
Rejection sampling avoids it: values that would fall in the uneven tail are discarded and drawn again. It costs nothing noticeable and makes the distribution genuinely uniform.
Unique numbers
Drawing without replacement is a different operation from drawing repeatedly and discarding matches — the second is slow and subtly wrong as the range fills up. Ask for unique numbers and you get a proper shuffle of the range.
Frequently asked questions
Is this genuinely random?
It uses the operating system’s cryptographic random source, which is as close to random as software gets. Math.random(), which most generators use, is predictable from a short sample.
Is the distribution even?
Yes. Rejection sampling is used rather than a remainder, which would make the lower part of the range slightly more likely.
Can I use this for a prize draw?
The randomness is sound. Whether it is defensible also depends on your being able to show how the draw was run, so record the process as well as the result.
Something wrong with this tool, or an idea for it? Tell us