Shuffle Isn't Random — JavaScript Bug Hunt

Inspired by Spotify's famous shuffle complaints — and the classic broken shuffle behind them.

  • Language: JavaScript
  • Layer: Frontend
  • Difficulty: Medium
  • Concepts: Algorithms, Randomness
  • Modelled on: Spotify
  • Visible tests: rng pinned high keeps the order (j === i each step); rng pinned low rotates deterministically; every track is preserved
  • Reward: 50 XP for a complete fix

Briefing

Inspired by Spotify's famous shuffle complaints — and the classic broken shuffle behind them. A proper Fisher–Yates shuffle draws index j uniformly from 0..i at each step; the shipped version draws from the whole array every time, biasing the result.

The RNG harness is locked and deterministic, so the correct permutations are exactly checkable.

Bug report

BUG-4096 · Reported by: playback QA

With the seeded RNG that always returns 0.99, a correct Fisher–Yates pass must leave the queue in its original order (j always equals i). Ours reorders it.

Statistical audit: the naive draw over the full length makes some permutations ~30% more likely than others.

Logs

[shuffle] rng=const(0.99) input=[1,2,3,4] -> [1,3,4,2] (expected [1,2,3,4])

The code as shipped

src/player/shuffle.js (editable)

// Fisher–Yates shuffle over a copy of the queue.
// rng() returns a float in [0, 1).
exports.shuffle = function (queue, rng) {
  var arr = queue.slice();
  for (var i = arr.length - 1; i > 0; i--) {
    var j = Math.floor(rng() * arr.length);
    var t = arr[i];
    arr[i] = arr[j];
    arr[j] = t;
  }
  return arr;
};

Read-only context: src/player/rng.js.

Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.