Unbiased Fisher-Yates Shuffle

Shuffle arrays in place or on a copy using crypto-backed rejection sampling to avoid modulo bias and preserve uniform permutations.

JavascriptData Integrity

Code

snippet
export function shuffle(items, options = {}) {
  if (!Array.isArray(items)) throw new TypeError("items must be an array");
  const { copy = true } = options;
  const result = copy ? items.slice() : items;
  for (let index = result.length - 1; index > 0; index -= 1) {
    const swapIndex = randomInteger(index + 1);
    const temporary = result[index];
    result[index] = result[swapIndex];
    result[swapIndex] = temporary;
  }
  return result;
}

function randomInteger(exclusiveMax) {
  if (!Number.isInteger(exclusiveMax) || exclusiveMax < 1 || exclusiveMax > 0x100000000) {
    throw new RangeError("exclusiveMax must be an integer between 1 and 2^32");
  }
  if (typeof globalThis.crypto?.getRandomValues !== "function") {
    throw new Error("crypto.getRandomValues is required for an unbiased shuffle");
  }
  const limit = Math.floor(0x100000000 / exclusiveMax) * exclusiveMax;
  const buffer = new Uint32Array(1);
  let value = 0;
  do {
    globalThis.crypto.getRandomValues(buffer);
    value = buffer[0];
  } while (value >= limit);
  return value % exclusiveMax;
}

const deck = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(shuffle(deck));
console.log(shuffle(deck, { copy: false }));
console.log(deck.length);