Collections
|
This section documents the Swift 6 language mode as shipped by Swift 6.3, as published in The Swift Programming Language at docs.swift.org, which is the reference these pages are written and verified against. 6.4-beta-only features are always flagged as such — never presented as baseline. This content was generated with the assistance of AI and should be verified against docs.swift.org before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Swift ships three collection types you reach for constantly — Array, Set and Dictionary — built on a
protocol hierarchy that lets a single set of higher-order operations (map, filter, reduce, and the rest)
work identically over all of them, over ranges, and over any type you write yourself.
Array
var shoppingList = ["Eggs", "Milk"] // type inferred as [String]
let emptyArray = [Int]() // explicit element type, empty
var threeDoubles = Array(repeating: 0.0, count: 3) // [0.0, 0.0, 0.0]
shoppingList.append("Flour") // mutation requires `var`
shoppingList += ["Baking Powder"]
shoppingList.insert("Maple Syrup", at: 0)
shoppingList[1...3] = ["Bananas", "Apples"] // range subscript replaces a run of elements
print(shoppingList.first ?? "empty", shoppingList.last ?? "empty", shoppingList.count, shoppingList.isEmpty)
let mapleSyrup = shoppingList.remove(at: 0)
for (index, item) in shoppingList.enumerated() {
print("Item \(index + 1): \(item)")
}
Assigning an Array to a new let/var, or passing it to a function, copies it — as with String and every
other Swift collection, that copy is only actually materialized, copy-on-write, the moment either side is
mutated.
Set
var letters = Set<Character>() // an empty set of Characters
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"] // set literal, type inferred
favoriteGenres.insert("Jazz")
if let removed = favoriteGenres.remove("Rock") { print("\(removed)? Never much cared for it.") }
print(favoriteGenres.contains("Classical"))
let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimes: Set = [2, 3, 5, 7]
print(oddDigits.union(evenDigits).sorted()) // all ten digits
print(oddDigits.intersection(evenDigits).sorted()) // []
print(oddDigits.subtracting(singleDigitPrimes).sorted()) // [1, 9]
print(oddDigits.symmetricDifference(singleDigitPrimes).sorted()) // [1, 2, 9]
Every Set element must conform to Hashable, providing a hashValue-backed == so membership and set-algebra
operations run in roughly constant time instead of scanning; unlike Array, a Set has no defined order, which
is exactly why it offers union/intersection/subtraction/symmetric-difference instead of index-based mutation.
Dictionary
var namesOfIntegers: [Int: String] = [:] // empty dictionary literal
namesOfIntegers[16] = "sixteen"
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
airports["LHR"] = "London Heathrow"
airports["LHR"] = "London Heathrow Airport" // updates the existing value
if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
print("The old value for DUB was \(oldValue).")
}
if let airportName = airports["DUB"] { // subscript access returns an Optional
print("The name of the airport is \(airportName).")
}
airports["APL"] = nil // assigning nil removes a key-value pair
for (airportCode, airportName) in airports { // iteration yields (key, value) tuples, order not guaranteed
print("\(airportCode): \(airportName)")
}
print(Array(airports.keys), Array(airports.values))
Mutability, ArraySlice, and Ranges as Collections
let fibonacci = [1, 1, 2, 3, 5, 8, 13]
let middle: ArraySlice<Int> = fibonacci[2...4] // [2, 3, 5] -- shares fibonacci's storage, no copy yet
print(middle.startIndex, middle.endIndex) // 2 5 -- indices are preserved from the original array
let backToArray = Array(middle) // convert once you plan to keep the slice around
print((1...5).map { $0 * $0 }) // ClosedRange is itself a Collection
print((0..<3).reduce(0, +)) // 3 -- so is Range
let/var govern collection mutability exactly as with String: a let array, set, or dictionary cannot be
mutated at all, even element-by-element. ArraySlice<Element> is Array’s counterpart to `Substring — a
non-owning, storage-sharing view meant for short-term use — and both Range<Bound> and ClosedRange<Bound>
conform to Collection (when Bound is a Strideable integer type), so the range operators from
Operators are themselves usable with every sequence algorithm on
this page.
The Sequence/Collection Protocol Family
Each protocol in the chain adds one capability, and conforming types pick up every algorithm written against the protocols they satisfy for free:
-
Sequence— a single-pass walk (for-in,makeIterator()); no guarantee it can be walked twice. -
Collection— refinesSequencewith a stablestartIndex/endIndex, subscript access by index, and multi-pass iteration. -
BidirectionalCollection— addsindex(before:), so it can be walked backwards. -
RandomAccessCollection— adds O(1) index arithmetic (Arrayconforms; a linked-list-backed collection typically would not). -
MutableCollection— adds a settable subscript, for in-place element replacement without changing length. -
RangeReplaceableCollection— addsinsert/remove/appendover arbitrary subranges, changing length.
Array conforms to all six; Set and Dictionary conform only through Collection (they have no meaningful
before/after order, so neither is BidirectionalCollection nor RandomAccessCollection, and neither supports
arbitrary subrange replacement).
Higher-Order Functions
let numbers = [1, 2, 3, 4, 5, 6, 7, 8]
let doubled = numbers.map { $0 * 2 } // [2, 4, 6, 8, 10, 12, 14, 16]
let evens = numbers.filter { $0 % 2 == 0 } // [2, 4, 6, 8]
let sum = numbers.reduce(0) { $0 + $1 } // 36
let sumShorthand = numbers.reduce(0, +) // 36, using an operator as a function value
let strings = ["3", "banana", "-7", "42"]
let parsed = strings.compactMap { Int($0) } // [3, -7, 42] -- drops the elements that fail
let nested = [[1, 2], [3, 4], [5]]
let flattened = nested.flatMap { $0 } // [1, 2, 3, 4, 5]
let sortedDescending = numbers.sorted(by: >) // [8, 7, 6, 5, 4, 3, 2, 1]
let firstEven = numbers.first(where: { $0.isMultiple(of: 2) }) // Optional(2)
print(numbers.contains(where: { $0 > 6 })) // true
lazy, zip, enumerated, and stride
let names = ["Anna", "Alex", "Brian", "Jack"]
let ages = [61, 32, 25, 43]
for (name, age) in zip(names, ages) { // pairs elements until the shorter sequence runs out
print("\(name) is \(age)")
}
for (index, name) in names.enumerated() {
print("\(index): \(name)")
}
for value in stride(from: 0, to: 10, by: 2) { print(value) } // 0 2 4 6 8 -- half-open, like ..<
for value in stride(from: 1, through: 10, by: 3) { print(value) } // 1 4 7 10 -- closed, like ...
let hugeRange = 1...1_000_000
let firstFiveSquares = hugeRange.lazy
.map { $0 * $0 }
.filter { $0 % 2 == 0 }
.prefix(5) // computed on demand, element by element
print(Array(firstFiveSquares))
Without .lazy, chaining map/filter over a huge sequence builds a complete intermediate array at every step;
.lazy defers each transformation until an element is actually consumed, which is what makes .prefix(5) above
touch only as many elements of hugeRange as it needs.
InlineArray and Span (Swift 6.2)
// InlineArray<count, Element>: a fixed-size, inline-storage array -- no heap allocation, no copy-on-write
// indirection -- useful where a small, statically-sized buffer matters, such as tight numeric code.
let fixed: InlineArray<4, Int> = [1, 2, 3, 4]
print(fixed[0], fixed.count)
// Span<Element>: a non-owning, safe view over contiguous memory -- a memory-safe alternative to
// UnsafeBufferPointer for reading contiguous storage without copying it or taking ownership.
func sum(of values: Span<Int>) -> Int {
var total = 0
for i in values.indices { total += values[i] }
return total
}
Both types arrived in the Swift 6.2 standard library as part of an ongoing push for allocation-free,
memory-safe alternatives to heap-backed collections and unsafe pointers; Span is covered again alongside its
unsafe-pointer relatives in
Memory Safety and Unsafe Pointers.
See Also
-
Strings and Characters —
String, which shares this page’sCollectionconformance style. -
Control Flow —
for-inover the collections introduced here. -
Functional Programming —
map/filter/reduceand friends, examined as a style rather than a per-type API. -
Generics — writing your own
Sequence/Collectionconformances and algorithms generic over them. -
Standard Library Overview — where these protocols and types sit among the rest of the standard library.