How to properly shuffle an array in Swift: from beginner to pro
\nHave you ever wondered how to create a random playlist in your app? Or maybe you needed to shuffle quiz questions so they appear in a new order each time? If you work with Swift, sooner or later you will face the task: how to shuffle an array. This question on Stack Overflow has thousands of views, and for good reason — there are more pitfalls here than meets the eye.
\nIn this article, we won't just show you how to call shuffle(). We'll look under the hood: break down algorithms, compare performance, learn how to do optimized shuffling, and understand when the standard method is evil and when it's the best thing humanity has invented. Let's go!
Why just "random" is a bad idea?
\nLet's say you have an array:
\nvar cards = ["Ace", "King", "Queen", "Jack"]\nThe most naive way is to create a new array and insert elements at random positions. It seems logical, right?
\nfunc naiveShuffle(_ array: [String]) -> [String] {\n var shuffled = [String]()\n for element in array {\n let randomIndex = Int.random(in: 0...shuffled.count)\n shuffled.insert(element, at: randomIndex)\n }\n return shuffled\n}\nOn small arrays (4-10 elements) this works. But as soon as the number of elements exceeds a hundred, you will feel the pain. Why? Because insert(at:) has O(n) complexity — every time you insert an element at the beginning or middle, all subsequent elements shift. The final complexity: O(n²). For an array of 1000 elements, that's 500,000 operations. For 10,000 — already 50 million. The app will start to lag, the user will leave, the rating will drop. Don't do this.
The built-in shuffle() method: your new best friend
\nStarting with Swift 4.2, we have the native shuffle() method (for mutable arrays) and shuffled() (returns a new array). Here's how it works:
var numbers = [1, 2, 3, 4, 5]\nnumbers.shuffle() // modifies the original array\nprint(numbers) // for example: [3, 1, 5, 2, 4]\n\nlet shuffledNumbers = numbers.shuffled() // new array\nprint(shuffledNumbers) // [4, 2, 1, 5, 3]\nUnder the hood, Apple uses the Fisher-Yates algorithm (also known as the Knuth shuffle). This is the gold standard of shuffling. How does it work? We iterate through the array from the last element to the first, for each one we select a random index from 0 to the current position and swap the elements. Complexity: O(n). For an array of 10,000 elements — just 10,000 operations. 5000 times faster than the naive approach!
\n\nFisher-Yates algorithm: explained simply
\nLet's implement it manually to understand the magic:
\nextension Array {\n mutating func customShuffle() {\n for i in stride(from: count - 1, through: 1, by: -1) {\n let j = Int.random(in: 0...i)\n swapAt(i, j)\n }\n }\n}\n\nvar deck = Array(1...10)\ndeck.customShuffle()\nprint(deck) // [6, 2, 9, 1, 8, 4, 7, 3, 10, 5]\nWhat's happening here? Let's say an array of 5 elements. We start at index 4 (the last one). We select a random number from 0 to 4, swap them. Then we move to index 3, select from 0 to 3, swap. And so on up to index 1. Each element is guaranteed to end up at a random position exactly once. No shifts, no repeated insertions — pure mathematics.
\nAn important nuance: in the Fisher-Yates algorithm, you cannot select a random index from 0 to count-1 at each step. Why? Because then some elements might stay in place, while others might be moved multiple times, breaking the uniformity of the distribution. Trust Apple's standard implementation — it's optimal.