Tuples
A Swift tuple groups multiple values of any type into a single compound value. Useful for ad-hoc returns, multi-value bindings, dictionary iteration. No need to declare a struct for one-off shapes.
Create, destructure, label, return
EXAMPLE
// 1) Create — ad-hoc grouping
let point = (x: 10, y: 20)
let person = ("Ada", "Lovelace", 32) // unnamed
let status = (code: 200, message: "OK")
// Type annotation
let result: (Int, String) = (42, "success")
// 2) Access — by index or label
print(point.x) // 10
print(point.y) // 20
print(person.0) // 'Ada'
print(person.1) // 'Lovelace'
print(person.2) // 32
print(status.code, status.message) // 200 OK
// 3) Destructure
let (firstName, lastName, age) = person
print(firstName, age)
// Ignore a value with _
let (_, last, _) = person
print(last)
// 4) Return multiple values from a function
func parse(_ s: String) -> (String, Int)? {
let parts = s.split(separator: ":")
guard parts.count == 2, let port = Int(parts[1]) else { return nil }
return (String(parts[0]), port)
}
if let (host, port) = parse("localhost:8080") {
print(host, port)
}
// 5) Labelled return — names + types
func divmod(_ a: Int, _ b: Int) -> (quotient: Int, remainder: Int) {
(a / b, a % b)
}
let result = divmod(17, 5)
print(result.quotient) // 3
print(result.remainder) // 2
// 6) Comparable / Equatable — generated when components are
let a = (1, 2)
let b = (1, 2)
print(a == b) // true
let pairs = [(1, 'a'), (2, 'b')] // [(Int, String)]
// 7) Use in switch / pattern matching
let code: (Int, String) = (200, "OK")
switch code {
case (200, _): print("Success")
case (404, _): print("Not found")
case (500...599, _): print("Server error")
case (_, "OK"): print("Generic OK")
case (let status, let msg): print("\\(status): \\(msg)")
}
// Bind matched values
let point = (3, 4)
switch point {
case (0, 0): print("origin")
case (_, 0): print("on x axis")
case (0, _): print("on y axis")
case (let x, let y) where x == y:
print("on diagonal at \\(x)")
case (let x, let y):
print("point (\\(x), \\(y))")
}
// 8) Tuples in collections
let coords = [(0, 0), (1, 1), (2, 2)]
for (x, y) in coords {
print("\\(x), \\(y)")
}
let dict = ["a": 1, "b": 2, "c": 3]
for (key, value) in dict { // each iteration is a tuple
print("\\(key) = \\(value)")
}
// 9) Tuples in higher-order functions
let pairs = [("Ada", 32), ("Bo", 28), ("Cy", 41)]
let names = pairs.map(\\.0) // ['Ada', 'Bo', 'Cy']
let ages = pairs.map { $0.1 } // [32, 28, 41]
let sorted = pairs.sorted { $0.1 < $1.1 } // by age
// 10) Zip — combine arrays into pairs
let names = ["Ada", "Bo", "Cy"]
let ages = [32, 28, 41]
for (name, age) in zip(names, ages) {
print("\\(name): \\(age)")
}
let pairs = Array(zip(names, ages)) // [(String, Int)]
// 11) Tuples vs structs — when to use which
// Tuple — local, ad-hoc, throwaway
func minMax(_ nums: [Int]) -> (min: Int, max: Int) {
(nums.min()!, nums.max()!)
}
// Struct — exposed in your API, reused, has methods or invariants
struct Range {
let min: Int
let max: Int
var size: Int { max - min }
}
// Use tuple for internal helpers; struct for public APIs.
// 12) Limitations
// ❌ Can't conform to protocols (use struct)
// ❌ Can't have methods or computed properties
// ❌ No initializer with custom logic
// ❌ No subclasses
// ❌ Recursion limit (tuples can't recursively contain themselves)
// 13) Tuple types
typealias Point = (x: Double, y: Double)
typealias Range = (min: Int, max: Int)
func midpoint(_ a: Point, _ b: Point) -> Point {
((a.x + b.x) / 2, (a.y + b.y) / 2)
}
// 14) Common patterns
// a) HTTP response
func fetch(_ url: URL) -> (data: Data?, error: Error?) {
do {
let data = try Data(contentsOf: url)
return (data, nil)
} catch {
return (nil, error)
}
}
// Better: use Result type for one-or-the-other
func fetchModern(_ url: URL) -> Result<Data, Error> {
do { return .success(try Data(contentsOf: url)) }
catch { return .failure(error) }
}
// b) Pagination cursor
func page(_ items: [Item], cursor: String?) -> (items: [Item], next: String?) {
let batch = Array(items.prefix(20))
let next = items.count > 20 ? items[20].id : nil
return (batch, next)
}
// c) Compare with tuple comparison
let versions = [(1, 0, 3), (1, 2, 0), (1, 0, 5), (2, 0, 0)]
let sorted = versions.sorted { $0 < $1 }
// Lexicographic compare — tuples are Comparable if components are.
// 15) Dictionary iteration
let inventory = ["apple": 5, "banana": 3, "cherry": 12]
for (name, count) in inventory where count > 5 {
print("\\(name): plenty (\\(count))")
}
// Transform via map
let doubled = inventory.map { (name, count) in (name, count * 2) }
// 16) Nested tuples
let snapshot: ((Int, Int), [String]) = ((100, 200), ["a", "b"])
let ((x, y), tags) = snapshot
// 17) Empty / unit tuple
let nothing: () = () // 'Void' is a typealias for ()
func say() -> () { print("hi") } // returns ()
func say2() { print("hi") } // same; () return implicit
// 18) Common bugs
// • Confusing positional access — use labelled tuples to avoid index mistakes
// • Returning a tuple instead of a Result for failure cases
// • Long tuples (5+) — switch to a struct for readability
// • Trying to add methods to tuples → compile error
// • Comparing differently-shaped tuples → compile error
// 19) Best practices
// ✅ Label tuple fields for clarity
// ✅ Use typealias for repeated tuple types
// ✅ Use tuples for LOCAL multi-value returns
// ✅ Use structs when:
// - Exposed in public API
// - Has methods or invariants
// - More than 3-4 fields
// - Need to conform to a protocol
// ✅ Use Result<T, Error> instead of (T?, Error?)
// ✅ Destructure in for / if let bindings
Why it matters
Tuples shine for ad-hoc multi-value returns and dictionary iteration. Label fields for clarity, use typealias for repeated shapes — but graduate to a struct as soon as you need methods, protocol conformance, or more than 3-4 fields.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…