← Solo Founder Skills

The shuffle that is not a shuffle

A free skill from the Solo Founder Skills pack — the complete file, not an excerpt. Download the .md

The most common randomisation bug in JavaScript:

const shuffled = items.sort(() => Math.random() - 0.5);   // WRONG

It looks fine and it's everywhere. It is not a shuffle. Sort comparators are expected to be consistent, and this one isn't, so the engine's sort produces skewed permutations.

Measured on four quiz options:

Position Chance the correct answer lands there
0 28.3%
1 28.2%
2 24.7%
3 18.8%

Uniform would be 25% each. In practice, always guessing the first option beat guessing at random — a player who noticed would have an edge, and the quiz was quietly unfair.

The fix

Fisher–Yates, once, in a helper:

function shuffle(a) {
  const out = [...a];
  for (let i = out.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [out[i], out[j]] = [out[j], out[i]];
  }
  return out;
}

Then replace every site. The bug usually appears more than once — one codebase had it in eleven places: shuffling quiz options, picking which questions appeared, choosing which items were offered, ordering menu choices. Each one skewed something slightly.

Don't blanket-replace on a pattern match. (Math.random() - 0.5) also shows up legitimately as noise in physics and jitter:

velocity += (Math.random() - 0.5) * 0.012;   // leave this alone

Check each hit is actually a sort comparator.

Test the distribution

Don't eyeball randomness. Count it:

const pos = [0,0,0,0], N = 200000;
for (let i = 0; i < N; i++) {
  const order = shuffle(['a','b','c','d'].map((t,i) => [t,i]));
  pos[order.findIndex(o => o[1] === 0)]++;
}
pos.forEach((c,i) => console.log(i, (c/N*100).toFixed(1) + '%'));

Spread above about 3 percentage points across positions means something's biased.

Use a proper generator for the test. A hand-rolled linear congruential generator produced 1–2 point "errors" that looked like a real bug but were the test's own fault. Math.random() is uniform enough for checking distributions — save the custom generator for when you need reproducible seeds, and validate it separately.

Check the odds you show people

If you display odds anywhere — a percentage, a progress bar, a chance of success — make sure they match what the code actually rolls. Simulate the real roll a few hundred thousand times and compare:

cred   0  shown 5.0%/40.0%/55.0%   actual 5.0%/40.0%/55.0%
cred 100  shown 40.0%/40.0%/20.0%  actual 40.0%/40.1%/20.0%

Worst error under half a percentage point means the display isn't lying. A shown probability that the outcome doesn't honour is worse than showing nothing, because people make decisions on it.

While you're in there

Check that a stated goal is actually reachable. A counter reading "3 of 5" tells players 5 is the target; if 5 happens 0.17% of the time, the interface is promising something that effectively never happens. Work out the real odds of every milestone you display.

Fourteen more like this

Shipping, verification, deployment and marketing skills — each one from something that actually went wrong. Every snippet executed before release.

Get the pack — $49 See what's in it