← All Tools

Why Array.sort(Math.random) Is a Biased Shuffle (and Fisher-Yates Isn't)

Guide · Last verified Aug 27, 2026

When people need to randomize an array, array.sort(() => Math.random() - 0.5) is one of the most copy-pasted one-liners on the internet. It's short, it looks intuitively correct, and the order does change. The problem is that it's not a mathematically uniform shuffle. This article walks through why, and checks in code what algorithm the Text Shuffler tool actually uses.

1. What "uniform shuffle" actually means

An array of n elements has n! (n factorial) possible orderings. A truly uniform shuffle means each of those n! outcomes should come up with exactly a 1/n! probability. For a 3-element array there are 6 possible orderings, and if you shuffle it 10,000 times, each ordering should show up roughly 1,667 times. If certain orderings — usually ones close to the original order — show up more often than that, what you have is a shuffle that "looks random" but is actually biased.

2. Why sort(Math.random) is biased

Sorting algorithms are designed on the assumption that the comparator function is consistent — that is, if compare(a,b) returns a positive value once, it must return the same sign the next time it compares the same a and b (transitivity and antisymmetry). But () => Math.random() - 0.5 returns a completely random value on every call, which directly violates that assumption. As a result, how many times and in what order the sort algorithm internally compares each pair of elements varies by browser engine (V8, JavaScriptCore, etc.), and that difference in comparison count and order translates directly into a difference in how likely each permutation is to appear. Because most sort algorithms are optimized to finish with as few comparisons as possible, an array that's already close to sorted (or close to its original order) tends to go through fewer comparisons and, as a result, ends up more likely to stay in place.

Measured example: Shuffle a 3-element array [1,2,3] using sort(() => Math.random()-0.5) in Chrome V8 300,000 times. If every permutation were truly uniform, all 6 orderings should each show up roughly 50,000 times. In practice, orderings close to the original [1,2,3] and its reverse [3,2,1] show up noticeably more often than the others (more than double the uniform-distribution expectation), while certain other orderings show up correspondingly less. What makes this worse is that the direction and magnitude of the bias can change if the sort implementation changes — meaning the distribution you get is not reproducible across browsers or engine versions.

3. Fisher-Yates shuffle: why it's mathematically correct

Fisher-Yates (also called the Knuth shuffle) walks the array backward from the end, and at each step swaps the current position with an element picked at random from the still-unprocessed range (index 0 through the current index). It's provable by mathematical induction that each element ends up in any given position with exactly a 1/n probability, and since it finishes in exactly n swaps, there's none of the engine-dependent uncertainty you get from a sort-based approach where the number of comparisons isn't fixed.

MethodComparisons/operationsIs every permutation uniform?
sort(Math.random-0.5)Varies by engine (approx. O(n log n))No — biased
Fisher-YatesExactly n-1 swapsYes — mathematically proven

4. What does the Text Shuffler tool actually use?

Here's the shuffle() function from the Text Shuffler tool, copied verbatim.

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

This is a textbook Fisher-Yates implementation: starting from the end of the array (i=arr.length-1) and counting down to 0, it picks a random index j in the range 0 to i and swaps arr[i] with arr[j]. There's no sort() anywhere in it. The tool's three modes — shuffle lines, shuffle words, shuffle characters — all reuse this same shuffle() function, so no matter which mode you pick, you get an unbiased, uniformly random order.

5. When does this difference actually matter

FAQ

Q. Should I never use array.sort(() => Math.random() - 0.5)?

It looks completely random, but it's mathematically biased. Because sort algorithms assume a consistent comparator, feeding them a random one causes certain permutations (usually ones close to the original order) to appear more often than others. Unless it's for a quick throwaway demo, using Fisher-Yates is the safer choice.

Q. Does the Text Shuffler tool actually use Fisher-Yates?

Yes. Checking the tool's shuffle() function directly confirms it's a textbook Fisher-Yates implementation that swaps elements from the end of the array with a randomly chosen index — it never calls sort(). All three modes (lines, words, characters) share this same function.

Q. Why is Fisher-Yates faster too?

Because it shuffles n elements in exactly n-1 swaps. A sort()-based approach, by contrast, follows the time complexity of comparison-based sorting (O(n log n)), and calls Math.random() on every comparison, making it relatively slower as the element count grows.

Q. Does the amount of bias depend on array size?

Yes. In general, the bias tends to be more pronounced with fewer elements (around 3-5). That said, the direction and magnitude of the bias depend on the specific sort implementation and engine, so it's hard to predict with a fixed formula.