Randomness in JavaScript — Why Math.random() Isn't Random Enough
Building a fair lottery picker requires more than Math.random(). Here's the difference between pseudo-random and cryptographically secure random, and when it matters.
Math.random() — Fast but Predictable
Math.random(); // 0.7234...
This uses a PRNG (pseudo-random number generator) — typically xorshift128+ in V8. It's fast but deterministic: given the same seed, the sequence is reproducible.
For games and UI animations, this is fine. For lottery picks and security tokens, it isn't.
crypto.getRandomValues() — Truly Random
function secureRandom() {
const array = new Uint32Array(1);
crypto.getRandomValues(array);
return array[0] / (0xFFFFFFFF + 1); // 0 to 1
}
This uses the OS entropy pool (hardware events, timing jitter) — genuinely unpredictable.
Fair Shuffle (Fisher-Yates)
The naive shuffle (sort(() => Math.random() - 0.5)) is biased. Use Fisher-Yates:
function fairShuffle(array) {
const arr = [...array];
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(secureRandom() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
Drawing Without Replacement
function drawWithoutReplacement(items, count) {
if (count > items.length) throw new Error('Cannot draw more than available');
return fairShuffle(items).slice(0, count);
}
Drawing With Replacement
function drawWithReplacement(items, count) {
return Array.from({ length: count }, () => {
const index = Math.floor(secureRandom() * items.length);
return items[index];
});
}
Random Number in Range (Without Modulo Bias)
Simple modulo creates bias because the range rarely divides evenly into the max integer:
function secureRandomInt(min, max) {
const range = max - min + 1;
const maxValid = Math.floor(0x100000000 / range) * range;
let value;
do {
const arr = new Uint32Array(1);
crypto.getRandomValues(arr);
value = arr[0];
} while (value >= maxValid); // Reject values in the biased range
return min + (value % range);
}
Try the random picker at toolzip.app/tools/random-picker.
Top comments (0)