How the wheel picks a winner

Press Spin on any wheel on this site and two things happen in a fixed order: the engine draws the winner, and then the wheel turns. This guide walks through the draw: where the bytes come from, how a byte becomes a position in your list, and what the animation is for. Every figure below is computed at build time by the same engine that runs the wheels. Where a draw is shown step by step, the bytes are scripted so you can follow them; live spins take theirs from crypto.getRandomValues.

The pick happens before the animation

The moment you press Spin, the engine picks a winning index from your entries. Only once that index exists does the wheel begin its roughly four-second, ease-out rotation, and that rotation is calculated to decelerate onto the segment that already won. The order matters because it removes every lever a person might imagine pulling: the wheel's resting position, the instant you clicked, the deceleration curve and the previous result are not inputs to the draw. The engine function that does the work is called spin. It returns the winning index, the winning label and the angle the presentation layer must land on, and nothing else on the page is consulted.

Where the random bytes come from

The draw needs raw randomness, and the engine asks for it as bytes. In the browser those bytes come from crypto.getRandomValues, the browser's cryptographically secure generator. The engine takes any byte source that fills a buffer, and ships the cryptographic one — a one-line wrapper around crypto.getRandomValues — as the default, so a page simply calls spin with your entries and the cryptographic source is used. The automated tests, and the worked examples below, pass a scripted list of bytes in its place instead, so a draw becomes a reproducible calculation.

From a byte to a segment: rejection sampling, step by step

A byte holds 256 values, and a wheel has N segments. The obvious shortcut — take the byte, divide by N, keep the remainder — is exactly what the engine refuses to do, for a reason covered in its own section below. Instead it uses rejection sampling, in three steps.

  1. Count the bits N needs. The engine works out the smallest number of bits whose range covers N: enough to write the number N−1 in binary. The dice wheel, with 6 faces and indexes 0 to 5, needs 3 bits; the 1–10 number wheel needs 4 bits. Every wheel this site can build — up to 100 entries — fits in 7 bits, so a candidate never costs more than 1 byte.
  2. Mask the excess. A byte has 8 bits; the engine keeps only the low bits it counted and discards the rest. The dice wheel's masked candidate is therefore always between 0 and 7, and the number wheel's between 0 and 15.
  3. Reject and redraw. If the masked candidate is N or larger, it is thrown away entirely — not reduced, not wrapped — and a fresh byte is drawn. The first candidate below N is the winning index in your list.

Worked example: the dice wheel, scripted bytes 7 then 3. Byte 7 is 00000111 in binary; masked to 3 bits it is 7, which is not below 6, so it is rejected. Byte 3 is 00000011; masked it is 3, which is below 6, so it is accepted. Through the real engine, uniformInt(6, …) returns index 3, and spin over the dice faces returns the label "4" with a target angle of 210° — the midpoint of the segment that index occupies. The remainder shortcut would instead have turned byte 7 into index 1, face "2", without redrawing.

Worked example: the 1–10 number wheel, scripted bytes 254 then 5. Here the mask keeps 4 bits, so the largest possible candidate is 15. Byte 254 is 11111110; keeping only its low 4 bits gives a candidate of 14, under the mask's ceiling but not below 10, so it is rejected. Byte 5 is 00000101, masked to 5 and accepted. The engine returns index 5, the label "6" on a wheel of 1 to 10, landing at 198°. A large byte is not automatically rejected, because the mask strips its high bits first: byte 200 (11001000) on the same wheel masks to 8 and is accepted at once as "9".

A one-entry wheel is the degenerate case: with nothing to choose between, the engine returns index 0 without drawing a byte at all, even when handed a byte source that throws if consulted.

Why the naive remainder favors low segments

The remainder shortcut fails quietly. If N divides the 256 byte values exactly, dealing them round-robin to the segments gives every segment the same number, and the remainder is uniform. If it does not, the deal comes out uneven: the first few segments get one extra value each and the last ones do not. The extras always land on the low-numbered segments — the ones whose index is below the leftover — so the bias is not random noise but a consistent tilt toward the top of your list. On the dice wheel the deal would come out as 43 byte values each for faces 1 through 4, 42 each for the rest. The tilt is small, and small is the point: it is a bias that nothing in the result would reveal.

Rejection sampling avoids it by never mapping a leftover value anywhere. A candidate at or beyond N is discarded rather than wrapped, so the accepted candidates land on every segment the same number of times and each entry's probability is exactly 1/N. The automated tests drive every possible byte value through the mapping and assert exactly that; the table below repeats the run at build time.

Acceptance stays above half

Throwing bytes away raises an obvious worry: could a spin stall, redrawing over and over? The mask prevents it. Because the engine keeps only as many bits as N needs, the largest masked candidate is always less than twice N — its ceiling is one below the smallest power of two that is at least N — so more than half of all candidates are accepted, redraws are rare, and spins stay instant. The figures below come from running every one of the 256 byte values through the engine's draw for each wheel and counting where they landed.

Wheel Entries (N) Bits kept Byte values per entry Accepted Rejected Acceptance
Coin flip wheel 2 1 128 256 0 100.00%
Dice wheel 6 3 32 192 64 75.00%
Number wheel, 1–10 10 4 16 160 96 62.50%
Letter wheel 26 5 8 208 48 81.25%
Custom wheel at the 100-entry cap 100 7 2 200 56 78.13%

The "byte values per entry" column is the fairness proof: on every row each entry is hit by the same number of byte values, which is what "exactly 1/N" means in practice. The lowest acceptance any wheel on this site can reach is 50.78% at 65 entries, one past a power of two, where the mask has just grown a bit wider than the list needs. Even there more than half of all candidates are accepted, and every entry is still hit 2 times out of 256. At the other end, the 2-face coin wheel rejects nothing: 1 bit covers both outcomes exactly.

Reading the result: geometry is for the eyes

After the draw, the engine hands the presentation layer the midpoint angle of the winning segment. The geometry is rigid: a wheel of N entries is divided into N equal segments of 360/N degrees, in list order, with boundary angles computed as exact multiples. On the 6-name sample list of the custom wheel, each name occupies 60° and wins with probability 16.67%; on the 6-face dice wheel each face has 60° and 16.67%; at the cap of 100 entries each segment is a 3.6° sliver with 1.00%. Those figures come from the same computeSegments call the wheels use.

The point of listing them is what they do not show: the angle and the probability are both consequences of the count, but the probability is not a consequence of the angle. A 3.6° sliver on a 100-entry wheel is exactly as likely as a 180° half-circle on a 2-entry wheel of equals, because the winner is picked by index from random bytes and the wheel is then rotated so that segment's midpoint lands under the pointer. Colors are cosmetic too: they cycle through a fixed 8-color palette, with one adjustment so the last and first segments never match, and no color is ever consulted by the draw.

Reduced motion proves the animation is presentation

If your system asks for reduced motion, the wheel skips the animation entirely and announces the identical draw immediately: the same spin function, the same byte source and the same index, minus the rotation. It is the clearest demonstration that the spinning was never the deciding part. If the animation were doing the choosing, removing it would have to change the mechanism; it changes nothing but the wait. The rotation exists so that a room of people can watch a result arrive together — worth having, but theater, and the site says so.

Common misreadings

  • "It keeps landing on the same segment, so it must be due elsewhere." Each spin is an independent draw from fresh bytes. The engine keeps no record of earlier results.
  • "Clicking at the right moment tilts it." The draw completes before the rotation begins, so the instant of the click is not an input to the pick.
  • "It was resting on that segment, so that segment had a head start." The resting position is where the previous animation stopped. The next draw does not read it.
  • "Adding an entry changes the odds for the entries already there." It changes N, which changes 1/N for every entry equally.

Where the mechanism stops

A cryptographically uniform draw is the whole of what the engine guarantees. There is no auditor, no seed escrow and no protection against re-spinning until a result is liked, so the site is not a certified gambling or raffle system: for casual fairness among people who trust each other's screens it is excellent; for money-staked drawings it is the wrong tool. For the checks you can run yourself, see Is the wheel rigged? How to check. If you need every entry drawn exactly once, Eliminator mode, explained covers how each spin removes its pick and why the survivor is still exactly 1/N for everyone.

The custom wheel on the landing page is the general case: paste a list of any length up to 100 entries and the three steps above run on it unchanged. The Number Wheel is the 1–10 example from this page, and the Dice Wheel is the 6-face example. Every other wheel on the site, and the rest of the guides, sit on the same engine.

Frequently asked questions

Is the winner decided before or after the wheel spins?

Before. The engine draws the winning index the instant you press Spin, and the animation is then calculated to decelerate onto that segment. With reduced motion on, the animation is skipped and the identical draw is announced immediately.

Can I influence the result by clicking at the right moment or spinning again quickly?

No. The instant of the click, the wheel’s resting position, the deceleration curve and the previous result are not inputs to the draw. Each spin is an independent draw from fresh random bytes.

What is modulo bias, in one sentence?

The tilt you get by taking a random value’s remainder when the range of values does not divide evenly by the number of segments: the low-numbered segments collect one extra value each. This site discards any candidate at or beyond N and draws a fresh byte instead.

Does a thin segment have a smaller chance than a fat one?

No. The winner is picked by index from random bytes, and the wheel is then rotated to that segment’s midpoint; size never enters the draw. On a 100-entry wheel each 3.6° sliver has exactly the same 1.00% chance as every other.

If the engine throws bytes away, can a spin get stuck redrawing?

No. The mask keeps only as many bits as N needs, so more than half of all candidates are accepted on every wheel this site can build; the lowest acceptance across every list size up to 100 entries is 50.78% at 65 entries, computed at build time from the engine.

How can I check any of this myself?

Turn on reduced motion and spin: the draw arrives with no animation, so the rotation was never deciding anything. Open your browser’s network tab and spin: no request carries your entries. The guide on whether the wheel is rigged walks through both checks.

A fair draw, not a wise one: the wheel picks each entry with exactly equal probability, but it cannot know your situation — do not delegate consequential decisions to it. Everything runs locally in your browser; see the methodology page for how the draw works.