In this article we'll be exploring how we can shuffle an array of items in multiple different ways using TypeScript, or JavaScript should you prefer.

Pre-Requisites:

  • An understanding of TypeScript or JavaScript
  • A basic understanding of For Loops and Arrays

The following examples are written in TypeScript, but they work in exactly the same way using plain JavaScript. You just simply need to remove the :type syntax from all function parameters.

Method 1: Fisher-Yates Sorting Algorithm

This algorithm's basic premise is to iterate over the items, swapping each element in the array with a randomly selected element from the remaining un-shuffled portion of the array.

Let's take a look at this in practice, as it'll help you visualise it better:


// declare the function 
const shuffle = (array: string[]) => { 
  for (let i = array.length - 1; i > 0; i--) { 
    const j = Math.floor(Math.random() * (i + 1)); 
    [array[i], array[j]] = [array[j], array[i]]; 
  } 
  return array; 
}; 
  
// Usage 
const myArray = ["apple", "banana", "cherry", "date", "elderberry"]; 
const shuffledArray = shuffle(myArray); 

console.log(shuffledArray); 

Explanation

First, you create a for loop. This will allow you to loop over each item in the array, swapping its position with another item in the array.

You then create the i variable assigning it the value of length of the array - 1.

You do this because we're starting at the last element of the array, and all array indexes start at 0 – so the last index would be 4 (as there are 5 items in the array).

If you were to try and access myArray[i] with i equalling 5 (the length) it would throw an exception stating there is no item at index 5. So we subtract 1 from the length.

By starting from the last element and working your way backwards, you guarantee that elements towards the end of the array have an equal chance of being swapped with any other element.

If you were to shuffle the array from the beginning to the end, the elements towards the beginning of the array would have a higher chance of being swapped multiple times, leading to a biased or uneven shuffle.‌‌‌‌ You then create a j variable which will be used for your index pointer for the big swap.

You then assign the array at index i to the array at index j, and visa versa. This swaps the values and shuffles them up for each item in the array.

Array Destructuring Assignment Explained

The syntax [array[i], array[j]] = [array[j], array[i]] is called an array destructuring assignment. It allows for the swapping of values between two variables or array elements without the need for a temporary variable.

Here's how the array destructuring assignment works in the context of shuffling an array using the Fisher-Yates shuffle algorithm:

  • array[i] and array[j] represent two elements in the array that need to be swapped.
  • [array[j], array[i]] creates a temporary array containing the values of array[j] and array[i], but in reverse order.
  • By assigning [array[j], array[i]] to [array[i], array[j]], the values of array[j] and array[i] are swapped in place.

You can learn more about array destructuring here.‌‌‌‌ If we were to print out the steps of the function step by step we would would get the following:

// starting array 
["apple", "banana", "cherry", "date", "elderberry"]; 

//1st Iteration - Swap elderberry with date 
i = 4; // elderberry 
j = 3; // date 
[("apple", "banana", "cherry", "elderberry", "date")]; 

// 2nd Iteration -  Swap elderberry with apple 
i = 3; // 
elderberry j = 0; // apple 
[("elderberry", "banana", "cherry", "apple", "date")]; 

// 3rd Iteration - Swap cherry with banana 
i = 2; // cherry 
j = 1; // banana 
[("elderberry", "cherry", "banana", "apple", "date")]; 

// 4th Iteration - Swap cherry with itself (stays where it is) 
i = 1; // cherry 
j = 1; // cherry 
[("elderberry", "cherry", "banana", "apple", "date")]; 

And there you have it – we have shuffled the array using the Fisher-Yates algorithm.

Method 2: Using the sort() Method with a Random Comparison Function

If you're not familiar with the JS sort function, I've written a tutorial on how to use this which you can find here.

const shuffle = (array: string[]) => { 
    return array.sort(() => Math.random() - 0.5); 
}; 

// Usage 
const myArray = ["apple", "banana", "cherry", "date", "elderberry"]; 
const shuffledArray = shuffle(myArray); 
console.log(shuffledArray);

This is a simple sorting() function that returns a random number, which would either work out as a negative, 0, or positive number.‌‌‌‌The sort() method internally compares pairs of elements in the array and determines their relative order based on the return value of the comparison function.

  • If the comparison function returns a negative value, the first element is considered smaller and should be placed before the second element in the sorted array.
  • If the comparison function returns a positive value, the first element is considered larger and should be placed after the second element in the sorted array
  • If the comparison function returns 0, the relative order of the elements remains unchanged.

What does Math.random() return?

When you call Math.random() it generates a pseudo-random number, because as you may know, nothing is really random haha.‌‌‌‌"Pseudo-random" means that the numbers generated appear to be random but are actually determined by a deterministic algorithm (which differs between JS engine implementations).‌‌‌‌The number it returns will always be a floating number, between 0 and 1. Floating numbers (commonly referred to as "floats") are numbers which can be positive or negative and can have a fractional part. Examples of floating-point numbers include 3.14, -0.5, 1.0, 2.71828, and so on.

Why do you subtract 0.5 from the result of Math.random()?

By subtracting 0.5 from the result of Math.random(), you introduce a random value between -0.5 and 0.5. This random value will cause the comparison function to return negative, positive, or zero values in a random manner for different pairs of elements. Consequently, the sort() method shuffles the array randomly.

Method 3: Using the JS Array.map() Function

The .map() function allows you to iterate over each element of an array and transform them into new values based on a provided mapping function. The map() function returns a new array with the transformed values, leaving the original array unchanged.

const shuffle = (array: string[]) => { 
    return array.map((a) => ({ sort: Math.random(), value: a }))
        .sort((a, b) => a.sort - b.sort)
        .map((a) => a.value); 
}; 

// Usage 
const myArray = ["apple", "banana", "cherry", "date","elderberry"]; const shuffledArray = shuffle(myArray); 
console.log(shuffledArray); 

Here, you loop over the array and use the same Math.random() function as you did in the above example within the map() function, returning an array of the objects with a sort number, and a value.‌‌‌‌You can then use the sort() function to sort the array based on these values, before calling the map() function again to create an array of values (that is, the string names).


const shuffle = (array: string[]) => { 
  const shuffled = array.slice(); 
  let currentIndex = shuffled.length; 
  let temporaryValue, randomIndex; 
  while (currentIndex !== 0) { 
    randomIndex = Math.floor(Math.random() * currentIndex); 
    currentIndex -= 1; 
    temporaryValue = shuffled[currentIndex]; 
    shuffled[currentIndex] = shuffled[randomIndex]; 
    shuffled[randomIndex] = temporaryValue; 
  }
  return shuffled; 
}; 

// Usage 
const myArray = ["apple", "banana", "cherry", "date", "elderberry"]; const shuffledArray = shuffle(myArray); 
console.log(shuffledArray);

Conclusion

I'd recommend the Fisher-Yates algorithm method, as it is has a low time complexity, as its all dependent on the size of the array. Its time complexity can be seen as O(n), which implies that doubling the input size will roughly double the execution time. Similarly, if the input size is halved, the execution time will be approximately halved as well.

Without going into too much detail:

  • O represents the order of growth or complexity class.
  • n refers to the size of the input, typically represented by the variable n.

Meaning that the complexity of the function changes as the n (input) changes in size – for example: Complexity x 2 (2 items in array) vs Complexity x 10 (10 items in array). So the larger the array the larger the complexity and time taken to shuffle.

This is worth noting when shuffling large arrays. It could be worth looking at other methods, or perhaps chunking the array and running the shuffling in parallel before piecing it back together.

This method also allows for easier shuffling of any type of array, not just string[] too. It would also work very nicely when using TypeScript generics. This allows for any type of array to be passed to the function, and shuffled.

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

const strings = ["apple", "banana", "cherry", "date", "elderberry"]; 
const users = [ { name: "John", surname: "Doe" }, { name: "Jane", surname: "Doe" }];

const shuffledArray = shuffle(strings); 
const shuffledObjects = shuffle(users); 

console.log(shuffledArray); 
console.log(shuffledObjects); 

I hope you this found this article useful, and have learnt how to easily shuffle an array of items. You could use this for multiple use cases such as:

  • Shuffle a playlist of songs
  • Shuffling a list of team members to determine a rota system
  • Creating a quiz of random questions / order-randomising data samples, for example customer reviews / feedback.

As always feel free to follow me on Twitter @gweaths.