Why sort(() => Math.random() - 0.5) Is Biased
The one-line shuffle everyone copies, measured — and the distribution it actually produces.
There is a one-line array shuffle that appears in thousands of codebases: sort the array with a comparator that returns a random positive or negative number. It is short, it is memorable, and it does not produce a uniform shuffle.
It is worth being precise about the failure, because the usual objection — that the result is not random enough — is vague and easy to dismiss. The result is genuinely non-uniform: some orderings come out substantially more often than others, and which ones depends on the sorting algorithm your runtime happens to use.
This page explains why the comparator breaks sorting, what the resulting distribution actually looks like, and what to use instead.
What a sort comparator is allowed to do
A sorting algorithm assumes its comparator defines a consistent ordering. Specifically it assumes three things: that comparing an item with itself gives equal, that if A is before B then B is after A, and that if A is before B and B is before C then A is before C. That last property is transitivity, and every efficient sorting algorithm relies on it to avoid comparing every pair.
A random comparator satisfies none of them. It can report A before B on one call and B before A on the next, and it will happily produce a cycle where A beats B, B beats C, and C beats A. Given inconsistent input, a sorting algorithm does not fail — it produces an arbitrary ordering that depends entirely on which comparisons it happened to make.
- Comparators must be consistent: the same pair must always compare the same way.
- Comparators must be transitive: A<B and B<C must imply A<C.
- A random comparator violates both, so the sort's assumptions no longer hold.
- The output is therefore a function of the sorting algorithm, not a uniform random ordering.
Why the result depends on the runtime
Because the ordering falls out of which comparisons the algorithm performed, the distribution is a property of the sorting implementation rather than of the shuffle. Different engines use different algorithms and different thresholds for switching between them, so the same one-line shuffle produces measurably different distributions in different browsers, and in different versions of the same browser.
That makes the bias unusually hard to reason about. There is no single wrong distribution to correct for — there is a distribution that changes when your runtime updates, which is the worst possible property for something whose only job is fairness.
One robust and widely reproduced observation is that elements tend to stay near where they started. Sorting algorithms are efficient partly because they do not compare every pair, so an element far from the point of a partition may simply never be compared with the elements at the other end, and therefore never moves far.
What the distribution looks like
The practical way to see this is a position matrix. Take a list of a few items, run the sort-random shuffle many thousands of times, and count how often each item ends in each position. In a correct shuffle every cell of that matrix should hold roughly the same number.
With a random comparator, the diagonal is heavy: item one is over-represented in position one, item two in position two, and so on. The size of that effect varies with list length and with the runtime, and the direction is consistent — items do not move as far as they should.
That is not a subtle statistical artefact. On short lists in some engines, the identity-ish orderings can come out several times more often than expected, which for a raffle drawn from a list means the person at the top of the list wins more often.
What to measure, and what correct looks like
- Take a 5-item list and run 100,000 shuffles.
- Correct: every item lands in every position about 20,000 times — a flat 5×5 matrix.
- sort-random: the leading diagonal is visibly heavier, and the pattern changes between browsers.
The variant that does work
There is a sorting-based shuffle that is genuinely uniform, and the distinction is instructive. Instead of a random comparator, give every element a random key once, then sort by that key. The comparator is now consistent and transitive — it compares two fixed numbers — so the sort behaves correctly, and since the keys were assigned uniformly at random, every ordering is equally likely.
That method is correct and slower than Fisher-Yates, because sorting is n log n while Fisher-Yates is n. It also needs distinct keys: if two elements draw the same key, the tie is broken by the sort's internal behaviour rather than randomly, which reintroduces a small dependence on the implementation.
So the correct summary is not that sorting cannot shuffle. It is that the comparator must be a consistent function of fixed random keys, not a random function of the pair.
- Random comparator: inconsistent, non-uniform, implementation-dependent. Do not use.
- Random keys then sort: correct, uniform, slower than necessary, needs distinct keys.
- Fisher-Yates: correct, uniform, linear time, the standard answer.
Why it survives despite being wrong
The one-liner persists because its output passes every casual inspection. A shuffled-looking array is a shuffled-looking array, and nobody looks at the position matrix. The bias only appears in aggregate over thousands of runs, which is precisely the situation nobody sets up before shipping a shuffle.
It also persists because the failure is not a crash. Code that produces a plausible wrong answer survives review far more readily than code that throws, and a shuffle is one of the purest examples of that: there is no output you could look at and know it was wrong.
The general lesson generalises well beyond shuffling: any function whose correctness is only visible in a distribution needs a distribution test, because no amount of reading the output will reveal it.
Frequently asked questions
Is the sort-random shuffle actually biased, or just inelegant?
Genuinely biased. It produces some orderings substantially more often than others, and the pattern depends on which sorting algorithm your runtime uses.
Why does the sorting algorithm affect the outcome?
Because the ordering falls out of which comparisons were performed. Different algorithms make different comparisons, so different engines give different distributions.
What does the bias look like in practice?
Elements tend to stay near where they started, so the leading diagonal of a position-frequency matrix is heavier than it should be.
Is there a sorting-based shuffle that works?
Yes — assign each element a random key once, then sort by that key. The comparator is then consistent and the result is uniform, though slower than Fisher-Yates.
Does it matter for a small list?
It matters most for a small list, because the proportional distortion is largest there and small lists are exactly where shuffles get used for raffles.
Will a different random source fix it?
No. The problem is the comparator's inconsistency, not the quality of the randomness feeding it.
How would I detect this in code I inherited?
Run a position-frequency matrix over 100,000 shuffles of a 5-item list. A flat matrix is correct; a heavy diagonal is this bug.
Is it ever acceptable?
Only where the ordering does not matter at all — in which case you did not need a shuffle. Anywhere fairness is claimed, it is not acceptable.
Tools that use this
Related guides
How Shuffling Actually Works
Fisher-Yates, one swap at a time, and why it is the only shuffle worth using.
Pseudo-Random vs Cryptographic Randomness
Two generators, two guarantees, and the surprisingly small list of cases where the difference bites.
How to Audit a Random Tool Yourself
Four checks you can run on any picker in a browser tab, without trusting a word of its copy.