Overview
There are many situations where you want to randomize the order of data, such as the order of questions in a quiz app, a bingo game, or the presentation order for a meeting. Simply using Math.random() often results in “bias,” meaning the shuffle is not truly fair. In this article, we will explain how to implement a standard algorithm whose fairness is mathematically proven: the Fisher-Yates Shuffle.
Specifications (Input/Output)
Fisher-Yates Shuffle
This algorithm works by iterating through the array from the end to the beginning and swapping the element at the “current position” with an element at a “random position” before it.
- Input: An array (any data type).
- Output: A randomized array.
- Complexity: $O(n)$ (extremely fast).
Methods to Avoid
You may often see the method array.sort(() => Math.random() - 0.5). Technically, this does not result in a true random distribution and introduces bias. You should avoid using this method in professional projects.
Basic Usage
It is best to define a general-purpose shuffle function for reuse.
/**
* Function to shuffle an array (Fisher-Yates Shuffle)
* @param {Array} sourceArr - The original array
* @returns {Array} A new shuffled array
*/
const shuffleArray = (sourceArr) => {
// Create a copy using spread syntax to avoid mutating the original
const array = [...sourceArr];
// Loop from the end of the array to the beginning
for (let i = array.length - 1; i >= 0; i--) {
// Generate a random index between 0 and i
const randomIndex = Math.floor(Math.random() * (i + 1));
// Swap elements using destructuring assignment
[array[i], array[randomIndex]] = [array[randomIndex], array[i]];
}
return array;
};
// Usage example
const numbers = [1, 2, 3, 4, 5];
const shuffled = shuffleArray(numbers);
console.log(shuffled); // Example: [3, 1, 5, 2, 4]
Full Code (HTML / JAVASCRIPT)
This is a tool for determining the “speaker order” for an internal Lightning Talk (LT) event. A fairly reordered list is generated every time you click the button.
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LT Order Shuffle</title>
<style>
.shuffle-container {
font-family: 'Segoe UI', sans-serif;
max-width: 400px;
padding: 25px;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #fafafa;
text-align: center;
}
h3 { margin-top: 0; color: #444; }
.speaker-list {
list-style: decimal;
padding-left: 1.5em;
text-align: left;
margin: 20px auto;
width: fit-content;
font-size: 1.1rem;
}
.speaker-item {
padding: 5px 0;
border-bottom: 1px dashed #ccc;
}
.btn-shuffle {
background-color: #e91e63;
color: white;
border: none;
padding: 12px 24px;
border-radius: 30px;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
transition: transform 0.1s, box-shadow 0.1s;
}
.btn-shuffle:active {
transform: translateY(2px);
box-shadow: none;
}
</style>
</head>
<body>
<div class="shuffle-container">
<h3>LT Speaker Order</h3>
<ol id="order-list" class="speaker-list">
</ol>
<button id="btn-shuffle" class="btn-shuffle">Shuffle Order 🎲</button>
</div>
<script src="lt_shuffle.js"></script>
</body>
</html>
JavaScript
/**
* Speaker Order Shuffle Script
* Implementation of the Fisher-Yates algorithm
*/
// 1. Speaker list (Initial data)
const speakers = [
'Mori',
'Komori',
'Nakamori',
'Omori',
'Hayashi',
'Kobayashi',
'Nakabayashi',
'Obayashi'
];
// DOM elements
const listElement = document.getElementById('order-list');
const shuffleButton = document.getElementById('btn-shuffle');
/**
* Function to randomize array order
* @param {Array} array target array
* @returns {Array} A new shuffled array
*/
const shuffleArray = (array) => {
// Create a copy to avoid destructive operations
const clone = [...array];
for (let i = clone.length - 1; i >= 0; i--) {
// Determine a random index
const rand = Math.floor(Math.random() * (i + 1));
// Swap elements with destructuring assignment
[clone[i], clone[rand]] = [clone[rand], clone[i]];
}
return clone;
};
/**
* Function to render the list
* @param {Array} items array to display
*/
const renderList = (items) => {
listElement.innerHTML = '';
items.forEach((item) => {
const li = document.createElement('li');
li.className = 'speaker-item';
li.textContent = `${item}`;
listElement.appendChild(li);
});
};
/**
* Handler for button click
*/
const onShuffleClick = () => {
// Execute shuffle
const newOrder = shuffleArray(speakers);
// Update display
renderList(newOrder);
};
// Initial render
renderList(speakers);
// Event listener
shuffleButton.addEventListener('click', onShuffleClick);
Custom Points
Adding animations where elements move around during the shuffle can significantly improve the User Experience (UX). If you have specific roles that must be fixed, such as the “Opening Greeting” or “Closing Remarks,” you can implement logic to exclude those elements from the shuffle and then join them back at the end.
Important Notes
The code provided creates a copy using [...array], which is safe. However, if you manipulate the input array directly without copying it, the original data at the source will also change. This is known as a destructive change. Furthermore, Math.random() generates pseudo-random numbers. If you require a very high level of security for things like password generation or casino games, you must use crypto.getRandomValues().
Advanced Usage
Verifying the Bias of Sort-based Shuffling
This code demonstrates why using sort for shuffling is not recommended.
// Note: This method is not recommended
const badShuffle = (arr) => arr.sort(() => Math.random() - 0.5);
// In many browsers, this results in certain patterns appearing more frequently.
Summary
When you need to shuffle an array, the golden rule is to use the Fisher-Yates Shuffle instead of self-made implementations or sort hacks. This algorithm involves iterating from the end of the array and swapping elements randomly using a for loop, Math.random(), and destructuring assignment. To ensure safety in your applications, always perform these operations on a copy of the original array to avoid destroying the source data. By keeping this function in your toolkit, you can handle any requirement for random data display with confidence and fairness.
