Swift foundations · intro

Control flow and pattern matching

Loops, conditions, and the switch statement — which in Swift is a pattern matcher, not a jump table.

11 min read13 min practice0/3 exercises4 recall cards

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
Guess firstAnswering before you read makes the explanation stick — even when you get it wrong.

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:

Swift

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")
}
Swift

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.

C-style, where forgetting break is a bug
switch (code) {
  case 1:
    handleOne();
    // forgot break — falls through!
  case 2:
    handleTwo();
    break;
}
Swift, where the default is safe
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.

Predict, then runA wrong prediction you have committed to is worth more than a right answer you read.

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)")
    }
}
Check yourself

Why does removing default: from a switch over an enum often make code safer?

Classify temperatures

Write the code

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.

Solution unlocks after 3 attempts

Describe a move

Write the code

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.

Solution unlocks after 3 attempts

First gap

Write the code

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.

Solution unlocks after 3 attempts
Connect itSaved on this device. Never graded.

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.

Checkpoint

You can now:

  • Loop over ranges, collections and dictionaries, with where filters 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.

How well do you know this now? Rating yourself honestly, then being tested on it, is how you find out where your intuition is wrong.