Math.random() vs crypto.getRandomValues(): Which One Should a Yes/No Picker Use?
I recently built a small yes/no picker, and one question came up early: which random function should I use? Both Math.random() and crypto.getRandomValues() work in the browser. Here is how they differ, and when it matte
I recently built a small yes/no picker, and one question came up early: which random function should I use?
Both Math.random() and crypto.getRandomValues() work in the browser. Here is how they differ, and when it matters.
The quick version
// Option 1: Math.random()
const pick1 = Math.random() < 0.5 ? "Yes" : "No";
// Option 2: crypto.getRandomValues()
function pick2() {
const a = new Uint32Array(1);
crypto.getRandomValues(a);
return a[0] % 2 === 0 ? "Yes" : "No";
}
Both give you roughly a 50/50 split. For a toy project, either is fine.
What is different?
Math.random()
- Fast and simple.
- Uses a pseudo-random generator. Its output is good enough for games and animations, but it is not designed to be unpredictable.
- Fine for a casual "heads or tails".
crypto.getRandomValues()
- Uses the browser's cryptographically secure random source.
- Harder to predict, and the quality is more consistent across browsers.
- Slightly more code, but still tiny.
For a yes/no picker, Math.random() would not break anything. I chose crypto.getRandomValues() anyway, because it costs almost nothing and removes any doubt about fairness.
Is % 2 biased?
A common worry is modulo bias. It appears when the range of random values does not divide evenly by the number of options.
A Uint32 has 2^32 possible values. That number is even, so % 2 splits it into two equal halves. For two options, there is no modulo bias.
If you had three options, % 3 would be slightly uneven, and you would need a rejection loop to stay perfectly fair:
function randInt(n) {
const a = new Uint32Array(1);
const limit = Math.floor(0x100000000 / n) * n;
do {
crypto.getRandomValues(a);
} while (a[0] >= limit);
return a[0] % n;
}
A tiny reusable picker
function randInt(n) {
const a = new Uint32Array(1);
const limit = Math.floor(0x100000000 / n) * n;
do {
crypto.getRandomValues(a);
} while (a[0] >= limit);
return a[0] % n;
}
function pickYesNo() {
return randInt(2) === 0 ? "Yes" : "No";
}
document.querySelector("#go").addEventListener("click", () => {
document.querySelector("#answer").textContent = pickYesNo();
});
<button id="go">Ask</button>
<p id="answer"></p>
Quick test: is it really 50/50?
let yes = 0;
for (let i = 0; i < 100000; i++) {
if (pickYesNo() === "Yes") yes++;
}
console.log((yes / 1000).toFixed(1) + "% yes");
You should see a number close to 50%, such as 49.8% or 50.2%.
Takeaway
- For casual use, either function works.
- For anything where fairness or unpredictability matters, use
crypto.getRandomValues(). - With two options,
% 2is safe. With other counts, use a rejection loop.
If you want to see this in action, I use the same approach in a free yes or no generator that has card, wheel and quick modes. If you prefer a one-click version, there is also a simple yes or no button.
What would you add? Let me know in the comments.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.