Control flow and pattern matching
Loops, conditions, and the switch statement — which in Swift is a pattern matcher, not a jump table.
By the end you will be able to
- Write for-in loops over ranges, collections and dictionaries, with where clauses
- Use switch with value bindings, ranges, tuples and where clauses
- Explain why Swift requires switches to be exhaustive
This switch handles 1 and 2 but not every other Int. What happens?
let n = 5
switch n {
case 1: print("one")
case 2: print("two")
}Loops
for-in walks anything that can be walked:
for i in 1...3 { } // 1, 2, 3 — closed range
for i in 0..<3 { } // 0, 1, 2 — half-open range
for name in ["a", "b"] { } // array elements
for (key, value) in prices { } // dictionary pairs
for character in "hi" { } // characters
You do not write for (int i = 0; i < n; i++) in Swift. If you need the index alongside the element, ask for it:
for (index, name) in names.enumerated() { }
A where clause filters without an inner if:
while and repeat-while exist and behave as you expect. repeat-while runs the body at least once — it is Swift's do-while, renamed because do was needed for error handling.
Switch is a pattern matcher
This is where Swift departs sharply from C. A switch case is not a constant to compare against; it is a pattern to match.
Ranges
switch score {
case 90...100: grade = "A"
case 80..<90: grade = "B"
default: grade = "C"
}
Tuples, with _ for "anything"
switch point {
case (0, 0): print("origin")
case (_, 0): print("on the x axis")
case (0, _): print("on the y axis")
default: print("somewhere else")
}
Value binding — capture what matched
switch point {
case (let x, 0): print("on the x axis at \(x)")
case (0, let y): print("on the y axis at \(y)")
case let (x, y): print("at \(x), \(y)")
}
where — an extra condition on a case
switch point {
case let (x, y) where x == y: print("on the diagonal")
case let (x, y) where x == -y: print("on the anti-diagonal")
default: print("off both diagonals")
}No implicit fallthrough
In C, forgetting break silently runs the next case. In Swift, each case ends by itself. If you genuinely want to continue into the next case, you say so with fallthrough — which is rare enough that seeing it should make you look twice.
switch (code) {
case 1:
handleOne();
// forgot break — falls through!
case 2:
handleTwo();
break;
}switch code {
case 1:
handleOne()
case 2:
handleTwo()
}The Swift version cannot have this bug. Each case is a complete branch, and fallthrough is an explicit opt-in that a reviewer will notice.
Exhaustiveness is a feature, not a chore
Requiring a default on an Int switch feels like bureaucracy. The payoff arrives with enums.
When you switch over an enum and handle every case explicitly — with no default — adding a new case to that enum later turns every such switch into a compile error. The compiler hands you a checklist of every place that needs updating.
Write default: instead and you get silent, wrong behaviour at runtime in every one of those places. Leaving out default is often the safer choice.
This loop uses continue outer. What does it print?
outer: for row in 1...3 {
for column in 1...3 {
if column == row { continue outer }
print("\(row)-\(column)")
}
}Why does removing default: from a switch over an enum often make code safer?
Classify temperatures
Print one line per reading using these bands: below 0 → freezing, 0–15 → cold, 16–25 → mild, above 25 → warm. Use a single switch with range patterns.
Describe a move
Given (from, to) coordinate pairs, print stayed when both are equal, horizontal when only the row is the same, vertical when only the column is the same, and diagonal otherwise.
First gap
Find the first missing number in a sorted sequence starting at 1, and stop looking as soon as you find it. Print the gap, or none if there is not one.
You have now seen exhaustive switch and optionals. Both are Swift moving a runtime failure into compile time. Write a few sentences describing what those two features have in common — and name one cost the approach imposes on you as a programmer.
You can now:
- Loop over ranges, collections and dictionaries, with
wherefilters and labels - Match on ranges, tuples, bindings and conditions in a
switch - Explain why exhaustiveness turns future changes into compile errors
- Say why Swift has no implicit fallthrough
Next up: collections — arrays, dictionaries and sets, and the functional methods that replace most of the loops you would otherwise write.