Skip to main content

    Lesson 4 • Beginner

    Control Flow 🔀

    By the end of this lesson you'll make decisions with if/else, wield Swift's exhaustive switch with ranges and pattern matching, repeat work with for-in, while and repeat-while, and safely unwrap optionals with guard let and if let — the logic skeleton of every real Swift program.

    What You'll Learn in This Lesson

    • Branch with if / else if / else and the ternary operator
    • Use Swift's switch: exhaustive, no fall-through, ranges, tuples, where & value binding
    • Loop with for-in over ranges and arrays
    • Choose between while and repeat-while correctly
    • Exit early and validate inputs with guard and guard let
    • Safely unwrap optionals using optional binding (if let)

    1️⃣ Conditionals: if / else

    A conditional runs code only when a test is true. The test must be a Bool — a true/false value — which you build with comparisons like >, >=, == (equal) and != (not equal), and combine with &&, ||, and !. Read this worked example and run it, then you'll write your own.

    Worked example: if / else if / else, comparisons, logical & ternary
    // if / else if / else — your program makes a decision.
    // Swift needs NO parentheses around the test, but braces { } are ALWAYS required.
    let temperature = 28
    print("temperature = \(temperature)")
    
    if temperature > 35 {
        print("🔥 Extremely hot — stay hydrated.")
    } else if temperature > 25 {
        print("☀️ Warm — perfect for the beach!")   // 28 > 25, so THIS line runs
    } else if temperature > 15 {
        print("🌤️ Mild — grab a light jacket.")
    } else {
        print("🧥 Cold — wear a coat.")
    }
    
    // A condition is anything that evaluates to a Bool (true / false).
    let age = 20
    let canVote = age >= 18            // >= means "greater than or equal to" → true
    print("canVote = \(canVote)")
    
    // Combine conditions with && (AND), || (OR), ! (NOT).
    let isWeekend = true
    let isSunny = false
    print("beach day? \(isWeekend && isSunny)")   // both must be true → false
    print("relax day? \(isWeekend || isSunny)")   // either is enough  → true
    
    // Ternary operator: condition ? valueIfTrue : valueIfFalse
    let label = age >= 18 ? "Adult" : "Minor"
    print("label = \(label)")
    Output
    temperature = 28
    ☀️ Warm — perfect for the beach!
    canVote = true
    beach day? false
    relax day? true
    label = Adult
    This is real code — run it for free atSwiftFiddleor in your own editor.

    Your turn. The program below is almost finished — fill in the blanks marked ___ using the // 👉 hints, then run it and check the expected output.

    🎯 YOUR TURN: a pass/fail check and a ternary
    // 🎯 YOUR TURN — replace each ___ then run it on SwiftFiddle.
    
    let score = 72
    
    // 1) Print "Pass" if score is 50 or more, otherwise "Fail".
    //    Fill in the condition (use >= for "greater than or equal to").
    if score ___ 50 {            // 👉 replace ___ with the >= operator
        print("Pass")
    } else {
        print("Fail")
    }
    
    // 2) Set message to "Adult" when age is 18+, else "Minor", using a ternary.
    let age = 16
    let message = age >= 18 ? ___ : ___   // 👉 two strings in "double quotes"
    print(message)
    
    // ✅ Expected output:
    //    Pass
    //    Minor
    Output
    Pass
    Minor
    Fill in the ___ blanks, then run it for free on SwiftFiddle and compare with the expected output.

    2️⃣ Swift's Powerful switch

    Swift's switch is in a different league from C or Java. Two rules shape everything: it must be exhaustive (cover every possible value — usually via a default: case), and there is no implicit fall-through (only the matching case runs, so you never write break). On top of that you can match ranges (90...100), match and destructure tuples, bind matched values with let, and add extra conditions with where.

    Worked example: switch with ranges, tuples, value binding & where
    // Swift's switch is exhaustive (must cover every case) and has
    // NO implicit fallthrough — only the matching case runs, no break needed.
    
    // (a) Match several values in one case.
    let day = "Wednesday"
    switch day {
    case "Saturday", "Sunday":
        print("🎉 Weekend — relax!")
    case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
        print("📅 Weekday — time to work!")   // Wednesday matches here
    default:
        print("❓ Unknown day")               // default makes it exhaustive
    }
    
    // (b) Match RANGES. 1...5 is closed (incl. 5); 1..<5 is half-open (excl. 5).
    let scoreValue = 85
    let grade: String
    switch scoreValue {
    case 90...100: grade = "A"
    case 80..<90:  grade = "B"     // 80 up to but NOT including 90 → 85 lands here
    case 70..<80:  grade = "C"
    default:       grade = "F"
    }
    print("Score \(scoreValue) → Grade \(grade)")
    
    // (c) Match TUPLES, bind values with let, and refine with where.
    let point = (2, 4)
    switch point {
    case (0, 0):
        print("At the origin")
    case (let x, 0):
        print("On the x-axis at \(x)")        // value binding: capture x
    case (let x, let y) where x == y:
        print("On the diagonal")
    case (let x, let y):
        print("Somewhere at (\(x), \(y))")    // binds both coordinates
    }
    Output
    📅 Weekday — time to work!
    Score 85 → Grade B
    Somewhere at (2, 4)
    This is real code — run it for free atSwiftFiddleor in your own editor.

    Now you finish a grade classifier. Fill in the missing range and the catch-all keyword:

    🎯 YOUR TURN: complete the grade switch
    // 🎯 YOUR TURN — finish the grade switch by filling in the ranges.
    
    let mark = 64
    let grade: String
    
    switch mark {
    case 70...100: grade = "Distinction"
    case ___:      grade = "Merit"        // 👉 60 up to (not incl.) 70  → 60..<70
    case 50..<60:  grade = "Pass"
    ___:           grade = "Fail"         // 👉 the catch-all keyword that makes it exhaustive
    }
    
    print("Mark \(mark) → \(grade)")
    
    // ✅ Expected output:
    //    Mark 64 → Merit
    Output
    Mark 64 → Merit
    Fill in the ___ blanks (think about which range contains 64), then run it on SwiftFiddle.

    3️⃣ Loops: for-in, while & repeat-while

    Loops repeat work. for-in walks through a sequence — a range like 1...5 or an array. while repeats while a condition holds, checking before each pass (so it can run zero times). repeat-while checks after, so its body always runs at least once. A range with ... includes its end; with ..< it stops just before it.

    Worked example: for-in over ranges & arrays, while, repeat-while
    // for-in over a CLOSED range (1 through 5 inclusive).
    for i in 1...5 {
        print("\(i) squared = \(i * i)")
    }
    
    // for-in over an ARRAY.
    let fruits = ["Apple", "Banana", "Cherry"]
    for fruit in fruits {
        print("🍎 \(fruit)")
    }
    
    // enumerated() gives (index, value) pairs.
    for (index, fruit) in fruits.enumerated() {
        print("\(index): \(fruit)")
    }
    
    // while — checks the condition BEFORE each pass (may run zero times).
    var countdown = 3
    while countdown > 0 {
        print("\(countdown)...")
        countdown -= 1
    }
    
    // repeat-while — runs the body FIRST, then checks. Always runs at least once.
    var attempts = 0
    repeat {
        attempts += 1
        print("attempt \(attempts)")
    } while attempts < 2
    
    print("🚀 Liftoff!")
    Output
    1 squared = 1
    2 squared = 4
    3 squared = 9
    4 squared = 16
    5 squared = 25
    🍎 Apple
    🍎 Banana
    🍎 Cherry
    0: Apple
    1: Banana
    2: Cherry
    3...
    2...
    1...
    attempt 1
    attempt 2
    🚀 Liftoff!
    This is real code — run it for free atSwiftFiddleor in your own editor.

    4️⃣ Optionals: guard let & if let

    An optional is a value that might be missing — its type ends in ?, and "missing" is written nil. You can't use an optional directly; you must unwrap it. if let (optional binding) runs a block only when a value is present. guard let is the early-exit twin: if the value is missing it forces you to leave the scope right away, and otherwise the unwrapped value stays available for the rest of the function — keeping your main logic flat instead of buried in nested ifs.

    Worked example: if let, guard let, and guard for validation
    // An OPTIONAL may hold a value OR nothing (nil). Its type ends with ?.
    // You must safely unwrap it before using the value.
    
    let input = "42"
    
    // if let — runs the block ONLY if the conversion succeeds (binds 'number').
    if let number = Int(input) {
        print("Parsed \(number)")        // Int("42") succeeds → 42
    } else {
        print("Not a number")
    }
    
    // guard let — the "early exit". If unwrapping fails, you MUST leave the scope.
    // The unwrapped value then stays available for the REST of the function.
    func greet(_ name: String?) {
        guard let name = name else {
            print("No name given")        // guard's else must exit (return here)
            return
        }
        print("Hello, \(name)!")          // 'name' is a real String from here on
    }
    
    greet("Ada")
    greet(nil)
    
    // guard also works on plain conditions — fail fast, keep the happy path flat.
    func checkAge(_ age: Int) {
        guard age >= 18 else {
            print("Too young")
            return
        }
        print("Welcome!")
    }
    
    checkAge(20)
    checkAge(15)
    Output
    Parsed 42
    Hello, Ada!
    No name given
    Welcome!
    Too young
    This is real code — run it for free atSwiftFiddleor in your own editor.

    Common Errors (and the fix)

    • "Switch must be exhaustive" — you didn't cover every value. Add a default: case, or (for an enum) list every case so the compiler knows nothing is left out.
    • Forgetting default on an Int/String switch — these types have unlimited values, so they always need a default: even if you think you covered everything.
    • Using = instead of ==if x = 5 is an assignment and won't compile as a condition. Comparing values needs the double-equals ==.
    • Force-unwrapping with ! instead of guard letlet n = Int(text)! crashes at runtime ("Unexpectedly found nil") the moment the text isn't a number. Unwrap safely with guard let or if let.
    • "'guard' body must not fall through" — every guard ... else block must exit the scope. End it with return, break, continue, or throw.

    Pro Tips

    • 💡 Prefer switch over long if/else if chains when you're testing one value against many cases — it's clearer and the compiler checks you covered everything.
    • 💡 Reach for guard let at the top of a function to validate inputs and bail early. It keeps the "happy path" flat and unindented.
    • 💡 Use where in a for loop to filter as you go: for i in 1...100 where i % 2 == 0 iterates only the even numbers.
    • 💡 Half-open ranges match array indices: an array of length n has valid indices 0..<n, so you never run off the end.

    📋 Quick Reference — Control Flow

    ConstructSwift Syntax
    if / elseif x > 5 { ... } else { ... }
    ternarylet y = cond ? a : b
    switchswitch v { case 1: ...; default: ... }
    switch rangecase 80..<90: ...
    for-in rangefor i in 1...10 { ... }
    for-in arrayfor item in array { ... }
    whilewhile x > 0 { ... }
    repeat-whilerepeat { ... } while x > 0
    if letif let n = Int(s) { ... }
    guard letguard let n = x else { return }

    Frequently Asked Questions

    Q: Do I need parentheses around an if condition in Swift?

    No. Swift does not require parentheses around the condition (unlike C or Java), so write 'if x > 5 { ... }'. The curly braces, however, are always required — even for a single-line body.

    Q: Why does my switch say it must be exhaustive?

    A Swift switch has to handle every possible value. For types with unlimited values (like Int or String) you cover the rest with a 'default:' case. When switching over an enum you can instead list every case, and then no default is needed.

    Q: Does Swift fall through to the next case like C does?

    No. Swift runs only the matching case and then stops — there is no implicit fallthrough and you never need 'break'. If you genuinely want the next case to run too, opt in with the explicit 'fallthrough' keyword.

    Q: What is the difference between 1...5 and 1..<5?

    1...5 is a closed range and includes 5, giving 1, 2, 3, 4, 5. 1..<5 is a half-open range and stops before 5, giving 1, 2, 3, 4. Half-open ranges are handy for array indices because an array of length n has indices 0..<n.

    Q: When should I use guard instead of if let?

    Use guard let for early exit: it unwraps a value and, if that fails, you must leave the scope (return/break/throw). The unwrapped value then stays in scope for the rest of the function, keeping your main logic flat and unindented. Use if let when you only need the value inside one small block.

    Q: What is the difference between while and repeat-while?

    A while loop checks its condition before each pass, so it can run zero times. A repeat-while loop runs the body once and then checks, so it always runs at least once — use it when the work must happen before the test makes sense.

    Mini-Challenge: FizzBuzz

    No blanks this time — just a brief and an outline. Write it yourself, run it, and check your output against the expected lines in the comments. FizzBuzz combines a loop, the remainder operator, and an if/else chain — exactly this lesson's toolkit.

    🎯 MINI-CHALLENGE: build FizzBuzz
    // 🎯 MINI-CHALLENGE: FizzBuzz (a classic interview warm-up)
    // Loop over the numbers 1 through 15 and, for each one, print:
    //   • "Fizz"     if it's divisible by 3   (use  n % 3 == 0)
    //   • "Buzz"     if it's divisible by 5
    //   • "FizzBuzz" if it's divisible by BOTH 3 and 5  (check this FIRST)
    //   • the number itself otherwise
    //
    // Hints: use  for n in 1...15  and an if / else if / else chain.
    //        % is the remainder operator;  15 % 5 == 0  is true.
    //
    // ✅ Expected output (first 6 lines):
    //    1
    //    2
    //    Fizz
    //    4
    //    Buzz
    //    Fizz
    
    // your code here
    Write your solution, then run it on SwiftFiddle and compare with the expected output in the comments.

    🎉 Lesson Complete!

    • if / else if / else and the ternary ?: make decisions
    • switch is exhaustive with no fall-through, and matches ranges, tuples, value binding & where
    • for-in iterates ranges and arrays; ... includes the end, ..< stops before it
    • while tests before, repeat-while tests after (runs at least once)
    • if let and guard let unwrap optionals safely — never force-unwrap user input with !
    • Next lesson: Functions & Closures — package logic into reusable, named blocks

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service

    Install LearnCodingFast

    Learn faster with the app on your home screen.