if / else if / else
Control flow in modern Swift: if/else, switch with pattern matching, for-in loops, while, and the guard statement that rewrites how you structure functions.
Control flow determines what path your code takes based on the current conditions. In this post we cover if, switch, loops and the powerful guard — a Swift-native tool that completely changes how you structure your functions.
if / else if / else
The basic conditional structure. In Swift conditions don’t need parentheses (though you can add them), but curly braces {} are always mandatory.
let temperature = 28
if temperature > 35 {
print("Very hot")
} else if temperature > 20 {
print("Pleasant temperature") // this prints
} else {
print("Cold")
}
// In Swift 5.9+ you can use if as an expression to assign a value
let description = if temperature > 35 {
"Hot"
} else if temperature > 20 {
"Pleasant"
} else {
"Cold"
}
print(description) // "Pleasant"
switch — much more powerful than in other languages
Swift’s switch is significantly more powerful than in languages like Java or C. No break needed, supports ranges, tuples and advanced pattern matching. Each case must be exhaustive — if you don’t cover all cases, the compiler requires it.
// Basic switch — no break needed
let weekday = 3
switch weekday {
case 1: print("Monday")
case 2: print("Tuesday")
case 3: print("Wednesday") // this prints
case 4: print("Thursday")
case 5: print("Friday")
case 6, 7: print("Weekend") // multiple values in one case
default: print("Invalid day")
}
// Switch with ranges
let grade = 78
switch grade {
case 90...100: print("Excellent")
case 70..<90: print("Good") // this prints
case 50..<70: print("Pass")
default: print("Fail")
}
// Switch with where (additional condition)
let value = 15
switch value {
case let x where x % 2 == 0:
print("\(x) is even")
case let x where x % 2 != 0:
print("\(x) is odd") // this prints
default:
break
}
for-in loop
The most used loop in Swift. Iterates over collections, ranges and sequences. Clean, safe and expressive.
// Iterate a range
for i in 1...5 {
print(i)
}
// Iterate an array
let languages = ["Swift", "Kotlin", "Python"]
for language in languages {
print(language)
}
// Ignore the index with _
for _ in 1...3 {
print("Repetition")
}
// Iterate with index using enumerated()
for (index, language) in languages.enumerated() {
print("\(index + 1). \(language)")
}
// Iterate a dictionary
let capitals = ["Mexico": "CDMX", "Spain": "Madrid", "Argentina": "Buenos Aires"]
for (country, capital) in capitals {
print("\(country) → \(capital)")
}
while and repeat-while loops
Used when you don’t know in advance how many iterations you need. while evaluates the condition before executing; repeat-while evaluates it after (guarantees at least one execution).
// while — evaluates first
var counter = 0
while counter < 5 {
print(counter)
counter += 1
}
// repeat-while — executes at least once
var attempt = 0
repeat {
print("Attempt \(attempt)")
attempt += 1
} while attempt < 3
break and continue
break stops the loop entirely. continue skips to the next iteration without executing the rest of the current cycle’s code.
// break — stops the loop
for number in 1...10 {
if number == 6 { break }
print(number) // prints 1, 2, 3, 4, 5
}
// continue — skips that iteration
for number in 1...10 {
if number % 2 == 0 { continue }
print(number) // prints only odd numbers: 1, 3, 5, 7, 9
}
guard — Swift’s crown jewel
guard is one of the most important elements in Swift and is absent in most other languages. Its job is to validate conditions at the start of a function and exit immediately if they’re not met. This eliminates excessive if nesting and makes code much more readable.
// Without guard — the "pyramid of doom"
func processWithoutGuard(name: String?, age: Int?) {
if let name = name {
if let age = age {
if age >= 18 {
print("Processing: \(name), \(age) years old")
} else {
print("Under 18")
}
} else {
print("Invalid age")
}
} else {
print("Invalid name")
}
}
// With guard — linear, easy to read
func processWithGuard(name: String?, age: Int?) {
guard let name = name else {
print("Invalid name")
return
}
guard let age = age else {
print("Invalid age")
return
}
guard age >= 18 else {
print("Under 18")
return
}
// Here we know name and age are valid
print("Processing: \(name), \(age) years old")
}
processWithGuard(name: "User", age: 25) // "Processing: User, 25 years old"
processWithGuard(name: nil, age: 25) // "Invalid name"
processWithGuard(name: "User", age: 15) // "Under 18"
The rule is simple: use guard for error cases or conditions that must be true for the code to make sense. Everything after the guard is already validated — the happy path stays clean and flat.
Summary
- if/else: simple conditions; in Swift 5.9+ can also be used as an expression to assign values.
- switch: exhaustive, no break, supports ranges and pattern matching with where.
- for-in: Swift’s main loop, iterates over any sequence.
- while / repeat-while: when the number of iterations is unknown.
- guard: validates conditions upfront and exits if they fail — eliminates nesting and improves readability.
In the next post we arrive at one of the most important and distinctive topics in Swift: Optionals.