DEV Community

toolzip
toolzip

Posted on Originally published at toolzip.app

Randomness in JavaScript — Why Math.random() Isn't Random Enough

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...
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

Drawing With Replacement

function drawWithReplacement(items, count) {
  return Array.from({ length: count }, () => {
    const index = Math.floor(secureRandom() * items.length);
    return items[index];
  });
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

Try the random picker at toolzip.app/tools/random-picker.

Top comments (0)