Today we’ll look at control flow statements like if, for, and switch. These statements are the decision makers of Swift, allowing you to create dynamic software. Understanding control flow means you can write code that can branch, loop, and make decisions based on the data it encounters.

What is Control Flow?

Control flow determines the order in which your code executes. Instead of just running line by line from top to bottom, control flow lets you skip sections, repeat operations, or choose between different paths based on conditions. It’s what transforms a simple list of instructions into intelligent software.

Swift provides several powerful control flow tools that work seamlessly with the operators and data types you’ve already learned.


Conditional Statements: Making Decisions

Conditional statements let your code make decisions. They’re like the crossroads in your program where you choose which path to take.

The if Statement

The most basic decision-maker is the if statement. It runs code only when a condition is true.

let playerLevel = 25

if playerLevel >= 20 {
    print("Battle pass unlocked!")
}

if-else: Two Paths

When you need to handle both true and false cases, use if-else:

let hasKeyCard = true

if hasKeyCard {
    print("Enter the bunker!")
} else {
    print("Access denied, requires key card.")
}

if-else if-else: Multiple Conditions

For more complex decision trees, chain multiple conditions:

let heroScore = 85

if heroScore >= 90 {
    print("Welcome to the Avengers! 🦸‍♂️")
} else if heroScore >= 80 {
    print("You're a S.H.I.E.L.D. agent!")
} else if heroScore >= 70 {
    print("You're a friendly neighborhood hero.")
} else {
    print("Keep practicing, rookie!")
}

Typical Use Cases:

  • User authentication and authorisation
  • Form validation and input checking
  • Feature flags and conditional functionality
  • Game state management (win/lose conditions)

Guard Statements: Early Exit

The guard statement is Swift’s way of handling early exits gracefully. It’s perfect for validation and preventing running code in invalid states.

Basic Guard:

func processUser(name: String?) {
    guard let userName = name else {
        print("Error: No name provided")
        return
    }
    
    print("Processing user: \(userName)")
    // Continue with the rest of the function
}

Multiple Conditions:

func authenticateUser(username: String?, password: String?) {
    guard let user = username,
          let pass = password,
          !user.isEmpty,
          !pass.isEmpty else {
        print("Authentication failed: Missing credentials")
        return
    }
    
    print("Authenticating \(user)...")
    // Authentication logic here
}

Reusing the Variable Name with guard let name else

If you want to reuse the same variable name after unwrapping, Swift lets you write:

guard let name else {
    print("No name provided")
    return
}
print("Hello, \(name)!")

This is handy when you want to keep your code concise and avoid introducing a new variable just for the unwrapped value.

Typical Use Cases:

  • Input validation at the start of functions
  • Unwrapping optionals safely
  • Early returns when preconditions aren’t met
  • API parameter validation

Switch Statements: Pattern Matching

Swift’s switch statements are incredibly powerful. They perform pattern matching on data.

Basic Switch:

let weatherCondition = "sunny"

switch weatherCondition {
case "sunny":
    print("Perfect day for a picnic! ☀️")
case "rainy":
    print("Better bring an umbrella ☔")
case "snowy":
    print("Time for hot chocolate ❄️")
default:
    print("Weather is unpredictable today")
}

Multiple Values:

let characterClass = "mage"

switch characterClass {
case "warrior", "paladin", "knight":
    print("You specialise in melee combat")
case "mage", "wizard", "sorcerer":
    print("You master the arcane arts")
case "rogue", "assassin", "thief":
    print("You excel at stealth and cunning")
default:
    print("Your class is unique and mysterious")
}

Range Matching:

let experience = 1250

switch experience {
case 0..<100:
    print("Rookie adventurer")
case 100..<500:
    print("Seasoned explorer")
case 500..<1000:
    print("Veteran warrior")
case 1000...:
    print("Legendary hero")
default:
    print("Experience level unknown")
}

Typical Use Cases:

  • State machine implementations
  • Processing different types of user input
  • Categorising data into groups
  • Handling various error types

Loops: Repeating Operations

Loops let you repeat operations efficiently, whether you’re processing collections or repeating actions a certain number of times.

For-In Loops: Iterating Collections

The most common loop iterates over collections:

let spells = ["Fireball", "Ice Storm", "Lightning Bolt", "Heal"]

for spell in spells {
    print("Casting \(spell)!")
}

Range-Based Loops:

// Print countdown
for countdown in (1...5).reversed() {
    print("\(countdown)...")
}
print("Launch! 🚀")

// Process items with indices
let inventory = ["sword", "potion", "scroll"]
for index in 0..<inventory.count {
    print("Slot \(index): \(inventory[index])")
}

While Loops: Condition-Based Repetition

Use while when you don’t know exactly how many iterations you need:

var lives = 3
var gameOver = false

while lives > 0 && !gameOver {
    print("You have \(lives) lives remaining")
    // Game logic here
    lives -= 1
    
    if lives == 1 {
        print("Last chance!")
    }
}

Repeat-While: Execute At Least Once

When you need to run the loop body at least once:

var playerInput: String
repeat {
    print("Enter a command (type 'quit' to exit):")
    playerInput = readLine() ?? ""
    print("You entered: \(playerInput)")
} while playerInput != "quit"

Typical Use Cases:

  • Processing arrays and collections
  • Reading user input until valid
  • Game loops and animation frames
  • Batch operations on data

Loop Control: Breaking and Continuing

Sometimes you need fine control over loop execution.

Break: Exit the Loop

The break statement lets you break out of the loop entirely.

let treasureLocation = 7

for room in 1...10 {
    print("Searching room \(room)...")
    
    if room == treasureLocation {
        print("Treasure found! 💰")
        break  // Exit the loop immediately
    }
}

Continue: Skip to Next Iteration

The continue statement lets you skip the rest of the current iteration but the loop will continue from the next iteration.

let forbiddenRooms = [3, 7, 9]

for room in 1...10 {
    if forbiddenRooms.contains(room) {
        print("Room \(room) is locked. Skipping...")
        continue  // Skip the rest of this iteration
    }
    
    print("Exploring room \(room)")
    // Room exploration logic here
}

Labeled Statements: Control Nested Loops

For complex nested loops, you can use labels. In the following code we label the top level for loop as gameLoop, then specify that we want to break out of the gameLoop, ultimately breaking out of both loops.

gameLoop: for level in 1...3 {
    print("Starting level \(level)")
    
    for enemy in 1...5 {
        print("Fighting enemy \(enemy)")
        
        if enemy == 3 && level == 2 {
            print("Boss appeared! Ending game.")
            break gameLoop  // Breaks out of both loops
        }
    }
}

Typical Use Cases:

  • Early termination when a condition is met
  • Skipping invalid or unwanted data
  • Complex nested loop control
  • Search algorithms (finding the first match)

Combining Control Flow

Real-world code often combines multiple control flow techniques:

func processPlayerActions(_ actions: [String]) {
    guard !actions.isEmpty else {
        print("No actions to process")
        return
    }
    
    for (index, action) in actions.enumerated() {
        print("Action \(index + 1):")
        
        switch action.lowercased() {
        case "attack":
            print("⚔️  Player attacks!")
        case "defend":
            print("🛡  Player defends!")
        case "heal":
            print("❤️  Player heals!")
        case "run":
            print("💨 Player runs away!")
            break  // Exit the loop
        default:
            print("❓ Unknown action: \(action)")
            continue  // Skip to next action
        }
        
        // Simulate some processing time
        if index < actions.count - 1 {
            print("Preparing next action...\n")
        }
    }
    
    print("All actions processed!")
}

// Usage
let playerActions = ["Attack", "Heal", "unknown", "Defend", "Run", "Attack"]
processPlayerActions(playerActions)

Best Practices

1. Prefer Guard for Early Returns:

// Good
func validateInput(_ text: String?) -> Bool {
    guard let input = text, !input.isEmpty else {
        return false
    }
    return input.count >= 3
}

// Less ideal
func validateInputNested(_ text: String?) -> Bool {
    if let input = text {
        if !input.isEmpty {
            return input.count >= 3
        }
    }
    return false
}

2. Use Switch for Multiple Conditions:

// Good
switch controllerType {
case "DualSense":
    configureDualSense()
case "DualShock":
    configureDualShock()
case "ThirdParty":
    configureThirdPartyController()
default:
    showControllerError()
}

// Less clean
if controllerType == "DualSense" {
    configureDualSense()
} else if controllerType == "DualShock" {
    configureDualShock()
} else if controllerType == "ThirdParty" {
    configureThirdPartyController()
} else {
    showControllerError()
}

3. Keep Conditions Readable:

// Good
let isValidUser = hasAccount && isEmailVerified && !isBanned
if isValidUser {
    allowLogin()
}

// Less readable
if hasAccount && isEmailVerified && !isBanned {
    allowLogin()
}

Summary

Control flow is what brings your Swift programs to life. Here’s what we’ve covered:

  • Conditional statements (if, else, guard) for making decisions
  • Switch statements for powerful pattern matching
  • Loops (for-in, while, repeat-while) for repeating operations
  • Loop control (break, continue) for fine-tuned execution
  • Best practices for writing clean, readable control flow

Master these concepts, and you’ll be able to build apps that respond intelligently to user input, process data efficiently, and handle complex business logic with confidence.

Happy coding!