Skip to content

Swift Control Flow — Conditionals, Loops, and Pattern Matching

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Swift Control Flow. We cover key concepts, practical examples, and best practices to help you master this topic.

Swift control flow provides powerful constructs for directing program execution, including if-else conditionals, switch statements with pattern matching and value binding, for-in loops with ranges and collections, while and repeat-while loops, guard statements for early exit, and control transfer with break, continue, and fallthrough. This tutorial covers each construct with runnable examples and explains Swift's unique features like interval matching, where clauses, and binding in switch cases.

What You'll Learn

  • Using if, else if, and else for conditional branching
  • Writing switch statements with pattern matching and value binding
  • Using for-in loops with ranges, collections, and stride
  • Implementing while and repeat-while loops
  • Using guard statements for early exit and optional unwrapping
  • Controlling flow with break, continue, and fallthrough
  • Applying where clauses for additional conditions

Why It Matters

Control flow is how you Express logic in code. Swift's switch statement is far more powerful than in C-like languages, supporting pattern matching, interval matching, tuple destructuring, and value binding. The guard statement enforces the early-return pattern that makes code more readable by reducing nesting. Mastering these constructs lets you write clearer and safer code.

Real-World Use

The input validation system in Doda Browser uses guard statements at the start of every function to validate parameters, switch statements with pattern matching to handle different URL scheme types, and for-in loops with where clauses to filter through browser tabs efficiently.

Learning Path

flowchart LR
  A[Collections] --> B[Control Flow\nYou are here]
  B --> C[Functions]
  style B fill:#f90,color:#fff

If-Else Conditions

Swift's if statement supports optional binding without forced unwrapping:

import Foundation

let score = 85
let hasPassed = true

if score >= 90 {
  print("Grade: A")
} else if score >= 80 {
  print("Grade: B")
} else if score >= 70 {
  print("Grade: C")
} else {
  print("Grade: F")
}

// Multiple conditions with logical operators
if score >= 80 && hasPassed {
  print("Excellent work!")
}

// Optional binding in if
let optionalValue: Int? = 42
if let value = optionalValue {
  print("Value exists: \(value)")
} else {
  print("Value is nil")
}

Output:

Grade: B
Excellent work!
Value exists: 42

Switch Statements

Swift's switch is powerful and exhaustive:

import Foundation

let number = 5

// Basic switch
switch number {
case 1:
  print("One")
case 2, 3, 4:
  print("Small")
case 5...10:
  print("Medium")
case let x where x > 10:
  print("Large: \(x)")
default:
  print("Unknown")
}

// Interval matching
let temperature = 72
switch temperature {
case ..<32:
  print("Freezing")
case 32..<50:
  print("Cold")
case 50..<70:
  print("Cool")
case 70..<85:
  print("Warm")
case 85...:
  print("Hot")
default:
  print("Unknown")
}

// Tuple pattern matching
let point = (x: 3, y: 3)
switch point {
case (0, 0):
  print("Origin")
case (_, 0):
  print("On X axis")
case (0, _):
  print("On Y axis")
case (-3...3, -3...3):
  print("Within 3x3 box")
case let (x, y) where x == y:
  print("On diagonal: (\(x), \(y))")
default:
  print("Outside: (\(point.x), \(point.y))")
}

Output:

Medium
Warm
Within 3x3 box

For-In Loops

Iterate over ranges, collections, and custom sequences:

import Foundation

// Range iteration
for i in 1...5 {
  print(i, terminator: " ")
}
print()

// Stride iteration
print("Every 2 steps:")
for i in stride(from: 0, to: 10, by: 2) {
  print(i, terminator: " ")
}
print()

// Collection iteration
let fruits = ["Apple", "Banana", "Cherry"]
for fruit in fruits {
  print("I like \(fruit)")
}

// Enumerated iteration
for (index, fruit) in fruits.enumerated() {
  print("\(index + 1). \(fruit)")
}

// Where clause
let numbers = 1...20
for number in numbers where number % 3 == 0 {
  print("\(number) is divisible by 3")
}

Output:

1 2 3 4 5
Every 2 steps:
0 2 4 6 8
I like Apple
I like Banana
I like Cherry
1. Apple
2. Banana
3. Cherry
3 is divisible by 3
6 is divisible by 3
9 is divisible by 3
12 is divisible by 3
15 is divisible by 3
18 is divisible by 3

While and Repeat-While

Use while for condition-based looping:

import Foundation

// While loop
var countdown = 5
while countdown > 0 {
  print(countdown, terminator: " ")
  countdown -= 1
}
print("Go!")

// Repeat-while (guaranteed at least one iteration)
var attempts = 3
var success = false
repeat {
  print("Attempt \(4 - attempts)...")
  attempts -= 1
  // Simulate random success
  success = Int.random(in: 1...10) > 7
} while attempts > 0 && !success

if success {
  print("Operation succeeded!")
} else {
  print("Operation failed after 3 attempts")
}

Output:

5 4 3 2 1 Go!
Attempt 1...
Attempt 2...
Attempt 3...
Operation succeeded!

Guard Statements

Guard provides early exit with the else clause:

import Foundation

func processUser(name: String?, age: Int?, email: String?) {
  guard let name = name, !name.isEmpty else {
    print("Error: Name is required")
    return
  }

  guard let age = age, age >= 18 else {
    print("Error: User must be at least 18 years old")
    return
  }

  guard let email = email, email.contains("@") else {
    print("Error: Valid email is required")
    return
  }

  // All validations passed - process the user
  print("Processing user: \(name), age \(age), email \(email)")

  // Guard also works with boolean conditions
  guard age < 120 else {
    print("Warning: Age seems unrealistic")
    return
  }

  print("User \(name) registered successfully")
}

processUser(name: "Alice", age: 25, email: "alice@example.com")
processUser(name: nil, age: 20, email: "test@test.com")
processUser(name: "Bob", age: 16, email: "bob@test.com")

Output:

Processing user: Alice, age 25, email alice@example.com
User Alice registered successfully
Error: Name is required
Error: User must be at least 18 years old

Control Transfer

Use break, continue, fallthrough, and return:

import Foundation

// Break in a loop
print("Finding first number > 5:")
for number in 1...10 {
  if number > 5 {
    print("Found: \(number)")
    break
  }
}

// Continue in a loop
print("Odd numbers:")
for number in 1...10 {
  if number % 2 == 0 {
    continue
  }
  print(number, terminator: " ")
}
print()

// Fallthrough in switch (rarely used)
let value = 2
var result = ""
switch value {
case 1:
  result += "One "
  fallthrough
case 2:
  result += "Two "
  fallthrough
case 3:
  result += "Three"
default:
  break
}
print("Fallthrough result: \(result)")

Output:

Finding first number > 5:
Found: 6
Odd numbers:
1 3 5 7 9
Fallthrough result: Two Three

Common Mistakes

  1. Missing default in switch: Swift switch must be exhaustive over all possible values. Add a default case for non-enum types.
  2. Using if-else chains when switch is clearer: For three or more conditions, a switch statement is usually more readable and more powerful.
  3. Not using guard for early returns: Deeply nested if-let chains are harder to read. Use guard to unwrap and exit early.
  4. Infinite loops with while: Ensure the loop condition changes within the body. Swift does not protect against infinite loops.
  5. Forgetting that switch does not implicitly fallthrough: After a case matches, execution exits the switch. Use fallthrough explicitly if needed.

Practice Questions

  1. What is the difference between break and continue?

    • break exits the loop entirely. continue skips the current iteration and proceeds to the next one.
  2. How do you write a switch case that matches a range?

    • Use interval syntax: case 1...5: matches values 1 through 5 inclusive.
  3. What is the purpose of the guard statement?

    • guard checks a condition and exits the current scope if it fails. It is used for early returns, input validation, and optional unwrapping.
  4. How does a where clause work in a for-in loop?

    • for x in collection where condition { } skips elements that do not satisfy the condition, similar to filter.
  5. Challenge: Write a FizzBuzz program that prints numbers 1 to 100, replacing multiples of 3 with "Fizz", 5 with "Buzz", and both with "FizzBuzz". Use a for-in loop with a switch statement inside.

Mini Project

Build an interactive guessing game:

  • Generate a random number between 1 and 100.
  • Use a repeat-while loop to keep asking the user for guesses.
  • Use switch to categorize guesses as "too high", "too low", or "correct".
  • Use guard to validate input (must be a number between 1 and 100).
  • Track the number of attempts and print a summary when the game ends.
  • Use break to exit when the user types "quit".

FAQ

Does Swift have a ternary operator?

Yes. Swift supports the ternary conditional operator: condition ? value1 : value2. Use it sparingly for short, clear conditions.

Can I use switch with strings?

Yes. Switch works with any equatable type including String, Int, enums, and custom types that conform to Equatable.

What is the difference between for-in and forEach?

for-in supports break, continue, and return. forEach is a closure-based method that does not support control transfer.

What's Next

Learn how to define and call functions in the Functions tutorial.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro