Part of Swift with Copilot

Swift: Swap random elements in array

Sandy LaneSandy Lane

Video: Swift: Swap random elements in array by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

Swift with Copilot: Swap Random Elements in Array

arr.swapAt(i, j) swaps two indices in place. For two random indices: pick i and j from arr.indices.randomElement(), then arr.swapAt(i, j). To shuffle the whole thing, just arr.shuffle().

A small exercise that touches randomness, in-place mutation, and array indices.

The Copilot prompt

// Swap two random elements in the array
var numbers = [10, 20, 30, 40, 50]

Copilot completes:

var numbers = [10, 20, 30, 40, 50]

let i = numbers.indices.randomElement()!
let j = numbers.indices.randomElement()!
numbers.swapAt(i, j)

print(numbers)
// e.g. [10, 40, 30, 20, 50]

Walkthrough

arr.indices

[10, 20, 30].indices
// 0..<3 — Range<Int>

indices returns the valid range of indices for the array. For an Array, that's 0..<count. For ArraySlice and other Collections, it might not start at 0.

randomElement()

[10, 20, 30].indices.randomElement()
// Optional<Int> — e.g. 1

randomElement() works on any Collection and returns nil for empty. Force-unwrap is OK for a non-empty array.

swapAt(_:_:)

var arr = [10, 20, 30, 40]
arr.swapAt(0, 3)
print(arr)
// [40, 20, 30, 10]

swapAt(i, j) swaps the elements at indices i and j in place. Constant time, no allocation. Swaps with itself (i == j) is a no-op.

The array must be varlet doesn't allow mutation.

Edge case: same indices

let i = numbers.indices.randomElement()!
let j = numbers.indices.randomElement()!
// i and j might be the same — swap is a no-op

For a "guaranteed actually-swap":

guard numbers.count >= 2 else { return }
let i = numbers.indices.randomElement()!
var j = numbers.indices.randomElement()!
while j == i {
  j = numbers.indices.randomElement()!
}
numbers.swapAt(i, j)

Loop until j != i. For small arrays this is fine; for huge it's still O(1) expected.

Shuffling the whole array

If you want to randomize all positions:

var arr = [1, 2, 3, 4, 5]
arr.shuffle()
print(arr)
// [3, 1, 5, 2, 4] — different each run

shuffle() is in-place. For a copy: arr.shuffled().

Internally uses Fisher-Yates: O(n), unbiased.

Manual Fisher-Yates

extension Array {
  mutating func myShuffle() {
    for i in stride(from: count - 1, through: 1, by: -1) {
      let j = Int.random(in: 0...i)
      swapAt(i, j)
    }
  }
}

Walk from the end backwards; for each i, pick a random j in [0, i], swap. Each element ends up in a uniformly-random position.

You won't write this in real code (shuffle() is built-in), but it's a classic algorithm worth recognizing.

Random sample without replacement

let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let three = Array(arr.shuffled().prefix(3))
// 3 unique random elements

shuffled().prefix(N) is the standard idiom for random sampling.

Random pair

let pair = Array(arr.shuffled().prefix(2))
let a = pair[0]
let b = pair[1]

Two distinct elements, randomly chosen.

Modifying via indices loop

var arr = [1, 2, 3, 4, 5]
for i in arr.indices {
  if Bool.random() {
    let j = arr.indices.randomElement()!
    arr.swapAt(i, j)
  }
}

A "lazy shuffle" — randomly choose to swap or not at each position. Not a uniform shuffle, but useful for some randomization patterns.

In-place vs return-new

var arr = [1, 2, 3]
arr.shuffle()           // mutates, returns Void

let arr2 = [1, 2, 3]
let shuffled = arr2.shuffled()   // new array, original unchanged

The <verb> form mutates in place; the <verb>ed form returns a new value. Same convention as sort/sorted, reverse/reversed.

Working with ArraySlice

var arr = [1, 2, 3, 4, 5]
arr[1...3].shuffled()    // returns shuffled slice — original unchanged

arr[1...3] is an ArraySlice, not a new array. To mutate the underlying storage:

var slice = arr[1...3]
slice.shuffle()    // mutates slice
arr.replaceSubrange(1...3, with: slice)

Or just operate on indices:

for i in 1...3 {
  let j = (1...3).randomElement()!
  arr.swapAt(i, j)
}

Common stumbles

Mutating let. let arr = [1,2,3]; arr.swapAt(0,1) — error. Use var.

Force-unwrap on empty. [].indices.randomElement()! crashes. Guard with arr.isEmpty first.

Same index swap. swapAt(2, 2) is a no-op, not an error. If you really need a different index, check.

Shuffled vs sorted. shuffled() returns randomly-ordered; sorted() returns ascending. Don't confuse.

Shuffling huge arrays. shuffle() is O(n) — fast, no concern. shuffled() allocates a copy, so 2x memory briefly.

ArraySlice indices not starting at 0. arr[1...3].indices is 1...3, not 0...2. Don't assume zero-based.

What's next

Episode 20: Pass a closure to a function. Higher-order functions, the foundation.

Recap

arr.swapAt(i, j) for in-place swap. arr.indices.randomElement() for a random valid index. For full shuffle: arr.shuffle() (or .shuffled() for a copy). For random sample without replacement: arr.shuffled().prefix(N). Watch out for force-unwrap on empty arrays and same-index swaps. Use var for in-place; let is immutable.

Next episode: passing closures.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.