What happens if you mutate an array while you loop over it?
Predict first
What does this print?
var a = [1, 2, 3]
for x in a {
if x == 1 { a.append(4) }
print(x, terminator: " ")
}
- 1 2 3
- 1 2 3 4
- Crash
Predict first
What is a afterwards?
var a = [1, 2, 3]
for x in a {
a.removeAll { $0 == x }
}
print(a)
- []
- [2]
- Crash
Mental model. The loop reads from a photocopy made at the start. Scribbling on the original does not change what it is reading.
The loop works on a snapshot. for x in a copies the array value when the loop starts. Because of copy-on-write that copy is just a reference, until you mutate a inside the loop: then the buffer is copied once and the loop keeps walking the original elements.
So the code does not crash, but it also does not see its own changes. To remove while scanning, use removeAll(where:) or build a new array with filter. This differs from Objective-C, where mutating an NSMutableArray during fast enumeration throws.
The loop iterates the snapshot taken at the start, so the appended 4 is never visited.
Each pass removes its own element from the live array while the loop walks the untouched snapshot, so everything is removed.
Common traps
- Assuming appended elements get visited. They never do; the snapshot does not contain them.
- Mutating a
NSMutableArraythe same way. That is a runtime exception, not a snapshot.
They might follow up with
- Is the snapshot a full copy? No. It is a retained reference until the first write, which is the whole point of copy-on-write.
- How do you delete by index safely? Walk the indices in reverse, or use
remove(atOffsets:).
Related questions
- 1.1.7 What is copy-on-write? Which types use it?
- 2.1.9 Sequence, Collection, BidirectionalCollection, RandomAccessCollection: what does each add?
← How does an Array grow, and when do reserveCapacity and… What is an ArraySlice, and why does slice[0] crash? →