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.
Code
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);Report a problem with this exampleOpens a pre-filled issue on GitHub.