LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 517 min read

Control Flow

Master if/else, Swift's powerful switch statement with ranges and where clauses, for-in and while loops, and control transfer statements.

Introduction

Control flow statements decide which code runs, and how many times. Swift's conditionals and loops will feel familiar if you've used another C-family language, but Swift's `switch` statement in particular is far more powerful than its equivalents elsewhere, supporting ranges, tuples, and conditional pattern matching directly.

This lesson covers `if`/`else`, the full power of `switch`, `for-in` and `while` loops, and the statements you use to jump around inside them.

if / else

An `if` statement in Swift does not require parentheses around the condition, but the curly braces are always mandatory — even for a single statement.

let temperature = 72
if temperature > 85 {
print("It's hot")
} else if temperature > 60 {
print("It's pleasant")
} else {
print("It's cold")
}
Output

Click Run to see what this code prints.

The switch Statement

Swift's `switch` must be exhaustive — it has to handle every possible case, either explicitly or with a `default` clause, which the compiler enforces. Unlike C, Java, or JavaScript, a matched case does not automatically fall through into the next one.

let grade = "B"
switch grade {
case "A":
print("Excellent")
case "B", "C":
print("Good")
default:
print("Needs improvement")
}
Output

Click Run to see what this code prints.

Ranges and where Clauses

Swift's `switch` can match numeric ranges directly, and a `where` clause lets you attach an extra condition to a case for genuinely powerful pattern matching.

let score = 82
switch score {
case 90...100:
print("A")
case 80..<90 where score % 2 == 0:
print("B (even score)")
case 80..<90:
print("B")
default:
print("C or below")
}
Output

Click Run to see what this code prints.

The `...` operator creates a closed range that includes both ends (90 through 100 inclusive), while `..<` creates a half-open range that excludes the upper bound (80 up to, but not including, 90).

for-in Loops

The `for-in` loop iterates over sequences — arrays, ranges, dictionaries, and any other type conforming to `Sequence`.

let fruits = ["Apple", "Mango", "Banana"]
for fruit in fruits {
print(fruit)
}
for i in 1...5 {
print("Count: \(i)")
}

while and repeat-while

A `while` loop checks its condition before each iteration; `repeat-while` (Swift's version of `do-while`) always runs the body at least once, checking the condition afterward.

var count = 0
while count < 3 {
print("count is \(count)")
count += 1
}
var attempts = 0
repeat {
attempts += 1
} while attempts < 1
print("attempts: \(attempts)")

Control Transfer Statements

Swift provides `break` and `continue` like most C-family languages, plus an explicit `fallthrough` keyword for the rare case where you actually want a `switch` case to fall into the next one.

for number in 1...10 {
if number % 2 != 0 { continue } // skip odd numbers
if number > 6 { break } // stop once we pass 6
print(number)
}
Output

Click Run to see what this code prints.

A First Look at guard

Swift also has a `guard` statement for early exits: it checks a condition, and if it fails, the `else` block must exit the current scope (with `return`, `break`, `continue`, or `throw`). `guard` is especially powerful when combined with Optionals — you'll see that fully in the Optionals lesson.

func checkAge(_ age: Int) {
guard age >= 18 else {
print("Must be 18 or older")
return
}
print("Access granted")
}
checkAge(15)
checkAge(20)
Output

Click Run to see what this code prints.

Common Mistakes

Avoid These Mistakes
  • Forgetting that Swift's `switch` must be exhaustive — leaving out a `default` (when needed) is a compile-time error, not a silent skip.
  • Assuming `switch` falls through automatically like C or JavaScript — it doesn't; use the explicit `fallthrough` keyword if you truly need that behavior.
  • Mixing up `...` (closed range, includes both ends) and `..<` (half-open range, excludes the upper bound).
  • Writing a `guard` statement whose `else` block doesn't actually exit the current scope — the compiler will reject it.

Best Practices

  • Prefer `switch` over long `if`/`else if` chains once you're checking more than two or three discrete cases.
  • Use `guard` for validating preconditions at the top of a function — it keeps the "happy path" unindented and readable.
  • Reach for `where` clauses in `switch` when a case needs an extra condition, instead of nesting an `if` inside the case body.
  • Choose `..<` when working with array indices (to avoid off-by-one, out-of-bounds errors) and `...` for inclusive counting ranges.

Frequently Asked Questions

It's a deliberate safety choice — accidental fallthrough in C-family languages is a well-known source of bugs. Swift requires you to opt in explicitly with the `fallthrough` keyword.

No, they're optional and generally omitted in idiomatic Swift. The curly braces around the body, however, are always required.

Both can check a condition, but `guard`'s else block is required to exit the current scope, which keeps validation code from nesting your main logic inside an `if` block.

Key Takeaways

  • if/else in Swift never requires parentheses but always requires braces.
  • switch must be exhaustive, does not fall through automatically, and supports ranges and where clauses.
  • for-in iterates over any Sequence; while checks first, repeat-while checks after the first run.
  • break, continue, and the explicit fallthrough control loop and switch execution.
  • guard performs early-exit validation and must leave the current scope when its condition fails.

Summary

You can now direct the flow of a Swift program with conditionals, powerful pattern-matching switches, and loops. Next, you'll learn how to package logic into reusable functions and closures.

Next Lesson →

Functions & Closures