LuckPickerSpin the Wheel

Blog

Why Math.random() Isn't Actually Random

The difference between pseudo-random number generators and cryptographic randomness, in plain language.

It's a common assumption that Math.random(), the random-number function built into JavaScript, is simply 'random' the same way flipping a coin is random. It isn't, and understanding the actual difference between a pseudo-random number generator and a cryptographically secure one is the whole reason this site's tools are built the way they are.

What Math.random() actually does

Math.random() is what's called a pseudo-random number generator (PRNG) -- a deterministic mathematical formula that takes an internal state (a seed) and produces a sequence of numbers that looks statistically random for most everyday purposes, without actually being unpredictable in a rigorous sense. Given the same starting internal state, a PRNG will always produce the exact same sequence of 'random' numbers -- the appearance of randomness comes entirely from the formula's mathematical properties, not from any genuinely unpredictable physical source.

That's not a flaw for most of what Math.random() gets used for -- shuffling a UI animation, adding a bit of visual jitter, generating a quick throwaway value. The problem shows up specifically when unpredictability itself is the actual requirement: a password, a raffle draw, or anything where someone might have an incentive to predict or influence the outcome.

Why predictability is a real, not theoretical, risk

Because most JavaScript engines' Math.random() implementations are built from well-documented, publicly known algorithms, and because their internal state has a limited number of possible values, it's been demonstrated in security research that observing enough consecutive outputs from Math.random() can, in some implementations, allow someone to reconstruct the internal state and predict future outputs. That's a meaningful problem for anything security-adjacent -- a password generator built on Math.random() could, in principle, produce passwords an attacker could predict, which defeats the entire purpose of generating a random password in the first place.

What crypto.getRandomValues does differently

crypto.getRandomValues, part of the Web Crypto API built into modern browsers, draws from your operating system's cryptographically secure random number generator (CSPRNG) -- the same category of randomness source used to generate encryption keys for secure communication. A CSPRNG is specifically engineered so that even if an attacker sees a long sequence of its previous outputs, they gain no meaningful ability to predict the next one. That property -- unpredictability even under active analysis -- is the actual bar 'cryptographically secure' randomness has to clear, and it's a meaningfully higher bar than 'looks statistically even in a chart.'

How this shows up across the tools on this site

Every tool here uses crypto.getRandomValues rather than Math.random(), and the difference matters most concretely for the password and PIN generators, where predictability would be a genuine security failure, and for anything involving a raffle or giveaway draw, where a predictable random source would undermine the actual fairness claim being made to entrants. It matters somewhat less for something like the random emoji generator, where the practical stakes of predictability are low -- but using the stronger, correctly-unpredictable source consistently everywhere is simpler and more honest than deciding tool-by-tool which situations 'need' real randomness and which don't.

Rejection sampling: the other half of the fairness story

Solving predictability doesn't automatically solve everything, though. A cryptographically secure byte is still just one of 256 possible values, and plenty of the things people actually want a random pick from -- 20 die faces, 195 countries, an arbitrary number range someone typed in -- don't split evenly into 256. Map a byte onto one of those ranges the quick way and the small handful of leftover values pile up at the bottom of the range, tilting the odds ever so slightly toward the earliest outcomes. It's a separate bug from predictability, and a cryptographically secure source does nothing to fix it on its own -- fixing it takes discarding whichever bytes fall in that uneven leftover zone and drawing again until one lands cleanly. Get both pieces right -- an unpredictable source, and a range-mapping step that doesn't play favorites -- and what comes out the other end is genuinely trustworthy in both senses at once.

A concrete example of the bias problem

Say you need a random number between 1 and 3. A naive approach might take Math.random() (which returns a decimal between 0 and 1), multiply by 3, and round down. This looks reasonable, but depending on exactly how the rounding and edge cases are handled, it's easy to accidentally introduce a small skew toward certain outcomes -- and because Math.random()'s underlying algorithm isn't built with cryptographic guarantees, even a 'correctly' implemented version of this approach still carries the theoretical predictability problem described above. Compare that to how the tools on this site handle a 3-outcome draw (like rock-paper-scissors): draw a random byte from crypto.getRandomValues, and if it falls outside the largest range that divides evenly by 3, discard it and draw again -- a small amount of extra work that guarantees both unpredictability and exact uniformity.

How to tell whether a random tool you're using elsewhere is trustworthy

As a general rule of thumb, prefer any random generator (on this site or elsewhere) that explicitly states it uses crypto.getRandomValues, the Web Crypto API, or an equivalent cryptographically secure source, especially for anything security-adjacent like passwords or PINs. A tool that doesn't mention its randomness source at all, or that's vague about it, is worth treating with more skepticism for anything where the stakes of predictability are real -- which is exactly the gap this site's methodology page and every individual tool page are built to close by being explicit about the specific mechanism each tool uses.

The takeaway for anyone building their own tools

If you're building anything yourself that needs genuine fairness or unpredictability -- a raffle script, a password generator, a game mechanic with real stakes -- the practical lesson here is simple: reach for crypto.getRandomValues (or your platform's equivalent cryptographically secure random source) by default, rather than defaulting to whatever random function happens to be most convenient. The extra step is small, and the fairness and security difference it makes is not.

Frequently asked questions

Is Math.random() ever actually a security problem in practice, or is this mostly theoretical?

It's been demonstrated in real security research that some Math.random() implementations can be predicted from observed output, which is why security-conscious applications (password generators, cryptographic operations) avoid it -- the risk is real, even if most casual uses of Math.random() never encounter an attacker actively trying to exploit it.

Does crypto.getRandomValues work in every browser?

It's part of the standard Web Crypto API supported by all modern browsers, which is why every tool on this site can rely on it without needing a fallback.

If crypto.getRandomValues is better, why does Math.random() still exist?

It's faster and simpler for the vast majority of use cases where true unpredictability isn't required -- visual effects, casual shuffling, quick throwaway values -- so it remains the right tool for those situations even though it's the wrong one for fairness- or security-sensitive randomness.

Tools mentioned in this guide