Method and results

Is this wheel actually random?

Every tool on SpinPicker claims each entry has an equal chance. This page is the evidence: 16,000,000 draws, a chi-squared goodness-of-fit test for every wheel size on the site, and the code that produced the numbers.

The short answer

Yes, within what a test of this size can show. 16 of 16 wheel sizes passed at a threshold of p = 0.001, and the largest deviation any single slice showed was 2.41% away from its expected share, on the hundred slice wheel where each slice is expected only 10,000 times. A test cannot prove fairness, only fail to disprove it, and this one failed to disprove it everywhere.

How the numbers were made

For each wheel size, draw randomInt(0, n-1) the stated number of times and compare the observed slice counts to a uniform expectation with a chi-squared goodness-of-fit test. The function under test is the one the site ships (lib/random-core.mjs), imported directly rather than re-implemented.

Entropy sourcecrypto.getRandomValues (Node WebCrypto, same API the browser uses)
Bias mitigationRejection sampling: any 32-bit draw at or above the largest exact multiple of the range below 2^32 is discarded and redrawn, so no slice inherits an extra source value.
Draws per wheel size1,000,000
Total draws16,000,000
Source codescripts/randomness-test.mjs
Run on

Results per wheel size

SlicesExpected eachLowestHighestLargest deviationChi-squareddfpResult
2500,000499,446500,5540.111%1.2310.2679Pass
3333,333.333332,555334,0040.233%3.2020.2017Pass
4250,000249,288250,6160.285%5.5130.1381Pass
5200,000198,916200,9240.542%11.8940.0182Pass
6166,666.667166,256167,0910.255%3.5750.6136Pass
7142,857.143142,608143,4460.412%5.0360.5403Pass
8125,000124,631125,4150.332%2.6970.9121Pass
10100,00099,189100,8010.811%17.9290.0361Pass
1283,333.33382,95083,9430.732%12.74110.3106Pass
1471,428.57170,92571,8670.705%13.49130.4107Pass
2050,00049,57950,4010.842%16.25190.6405Pass
2441,666.66741,34542,1801.232%25.67230.3167Pass
2638,461.53838,10338,9231.200%37.90250.0473Pass
3033,333.33332,83833,7231.486%44.12290.0358Pass
5020,00019,71120,2871.445%53.07490.3201Pass
10010,0009,75910,2232.410%100.98990.4257Pass

What could have gone wrong, and did not

The realistic failure mode for a wheel is not a broken random number generator. It is a broken mapping onto the slices. A browser hands back a 32-bit integer, and folding that onto a range with a plain remainder is only fair for ranges that divide 232 exactly. Two, four and eight do. Three, seven, ten, twenty-six and one hundred do not, and for those the lowest-numbered slices would quietly collect a few extra outcomes on every spin, forever, in a way nobody would ever catch by eye.

That is why the sizes tested above include the awkward ones. A test that only checked coins and eight-slice wheels would pass trivially while a real bias sat in the hundred slice raffle wheel. Rejection sampling is what removes it: any draw landing in the incomplete final block of the 32-bit range is thrown away and redrawn, so every slice ends up backed by exactly the same number of source values.

Frequently asked questions

Does a chi-squared test prove the wheel is fair?

No test proves fairness; it can only fail to find bias. What this test shows is that across 16 million draws no wheel size deviated from a uniform distribution by more than chance would explain. That rules out the failure modes that actually occur in practice, in particular modulo bias on a range that does not divide evenly into 2 to the power 32.

Why does one of the p-values look low?

Under a genuinely fair generator the p-value is uniform between 0 and 1, so roughly one test in twenty lands below 0.05 purely by chance. That is the test behaving correctly. A single low p-value in one run is expected noise; a p-value that sits near zero for the same wheel size run after run would be the real signal, and that is the pattern the check mode looks for.

What is modulo bias and why does it matter here?

The browser hands back a 32-bit number. Folding that onto a range with a plain remainder is only fair when the range divides 2 to the power 32 exactly, which it does for 2, 4 and 8 but not for 3, 10, 26 or 100. Without a correction the low-numbered slices on a 100 slice wheel would come up marginally more often, forever. SpinPicker discards any draw at or above the largest exact multiple of the range and draws again, so every slice maps onto the same number of source values.

Is Math.random used anywhere?

Only as a fallback for an environment with no WebCrypto, which no browser able to run this site has. The specification does not require Math.random to be uniform or unpredictable, and implementations have historically been neither, which is why it is not the primary source.

Can I run this test myself?

Yes. It lives in scripts/randomness-test.mjs and imports the same function the site ships rather than a copy of it, so there is no gap between the code under test and the code doing the spinning. Run it with a different draw count and you get different numbers with the same conclusion.

Where this applies

One randomness primitive drives every tool on the site: the spinner wheel, the random name picker, the team generator, the dice roller, the coin flip and the number generator. Tested once, used everywhere.

Reproducing this: run node scripts/randomness-test.mjs in the repository. It writes data/randomness-report.json, which is what this page renders. No figure on this page is typed in by hand.