Blog/Technical

How Random Number Generators Actually Work (and Why It Matters for Wheels)

Math.random() is fine for shuffling card games. It is not fine for picking giveaway winners. Here is the difference.

Published February 25, 20269 min read

"Random" is one of those words that hides a surprising amount of engineering. The number that ends up determining who wins your giveaway is the output of a software process, and not all such processes are equal. This post is a non-technical-but-honest walk through what is actually happening when a wheel "spins" — and why the choice of random number generator is the difference between a fair draw and a statistically rigged one.

There is no such thing as randomness in code

A computer is a deterministic machine. Given the same input, the same program produces the same output — every time, forever. That is not a metaphor. It is what "deterministic" means. So when you ask a program for a "random" number, it cannot give you one in the strict sense of the word. What it can give you is a number that looks random — one that is hard to predict from the outside, distributed evenly over the range you asked for, and uncorrelated with the previous number it gave you.

How well a particular generator achieves those three properties is the entire game.

Pseudo-random number generators (PRNGs)

A PRNG starts with a "seed" — usually some easily-available number like the current time in milliseconds — and runs it through a fixed mathematical formula. The output of the formula is your "random" number, and it also becomes the seed for the next call. This is fast, cheap, and works well enough for most software purposes: shuffling a list, picking a random color, dithering a graphic.

It also has two important properties that matter for fair draws:

  • Same seed, same sequence. If you know what the seed was, you can reproduce every "random" number the program will ever produce. This is great for debugging. It is bad for fairness.
  • Predictable structure. Many simple PRNGs have detectable statistical patterns — the same numbers come up slightly more often, or pairs of numbers correlate in subtle ways — that an attacker can exploit if the stakes are high enough.

JavaScript's Math.random() is a PRNG. The exact algorithm depends on the browser (V8, SpiderMonkey, JavaScriptCore all use different ones), but they're all in the same family. For an animation or a casual game, that is completely fine. For a giveaway draw, it is not.

Cryptographically secure random number generators (CSPRNGs)

A CSPRNG is built to a much higher standard. It draws "entropy" — unpredictable noise — from physical sources the operating system can sample: timing jitter on hardware interrupts, mouse movement, network packet arrival times, sometimes dedicated hardware noise generators. That entropy is then fed into a cryptographic primitive (typically a hash function or block cipher in counter mode) that produces a stream of bytes with three guarantees:

  • No detectable structure. The output is, by every statistical test we know, indistinguishable from true randomness.
  • Forward and backward secrecy. Knowing one output does not let you predict the next, and it does not let you reconstruct the previous one.
  • No reproducible seed. You cannot replay the sequence even if you know what the program did, because the entropy that fed into it came from the physical world.

In the browser, you get this through the Web Crypto API: crypto.getRandomValues(). Under the hood it calls into the operating system's CSPRNG (BCryptGenRandom on Windows, /dev/urandom on Linux and macOS). It is the same source of randomness that browsers use to generate session keys for HTTPS connections.

Why this matters for a spinning wheel

When you spin a wheel, the program needs a number — say, between 0 and 1 — that determines where the pointer lands. If you use a PRNG, three things become possible that should make you nervous:

  1. Time-correlated outcomes. If the seed is the current millisecond and a determined entrant knows roughly when you'll spin, they can narrow down the result.
  2. Statistical bias over many spins. A subtly non-uniform PRNG will favor certain regions of the wheel — and over hundreds of giveaways, this becomes detectable.
  3. Deterministic replay. If anyone ever obtains the program's state, they can run the next spin in advance.

With a CSPRNG, none of those are possible. The number that picks the winner is genuinely unpredictable even to the person running the wheel, and there is no internal state that, if leaked, would compromise the next draw.

A common confusion: "but the animation is predictable!"

On a well-built wheel, the result is decided before the animation starts. The animation is a cosmetic countdown — the program already knows where the pointer is going to land, and the deceleration curve is calculated to bring it there. So the visible spinning has no influence on the outcome and cannot be "manipulated" by clicking faster or slower.

This is why a real-time animation length of 3 to 8 seconds does not weaken the randomness — the randomness happened in a single function call at the moment you pressed the button, and everything after is a UI flourish.

How to verify the wheel you're using

If a wheel tool publishes its source code (or you can inspect it via the browser's developer tools), look for one of these patterns:

// CSPRNG — what you want for fair draws
const buf = new Uint32Array(1)
crypto.getRandomValues(buf)
const r = buf[0] / 0xFFFFFFFF  // a uniform float in [0, 1]

// PRNG — fine for casual use, not for giveaways
const r = Math.random()

If you see Math.random() being used to pick a winner in any tool that markets itself for giveaways, that is a real warning sign. Not because it will obviously rig the result, but because the operator did not think carefully enough about the difference.

The trade-offs of CSPRNGs

CSPRNGs are slightly slower than PRNGs — by single-digit microseconds, which is irrelevant for a wheel spin. They also cannot be seeded, which means you cannot reproduce a specific spin "for testing." For a production fairness tool, that is a feature, not a limitation.

Where Wheelio sits

Wheelio uses crypto.getRandomValues() for every spin. This is a deliberate choice driven by the use cases — giveaways, classroom cold-calls, decision-making — where being able to defend the draw matters more than shaving microseconds off the call.

The short version

Pseudo-random is good enough when nothing is at stake. Cryptographic randomness is what you want when someone might one day ask "how do I know that wasn't fixed?" The difference costs nothing in practice and gives you an answer for that question that holds up.