Start learning free

LESSON 83 OF 415 · CONDITIONAL STATEMENTS

Optionals and Unwrapping in Swift: if let, guard let, ??

Lesson Read the full explanation

In many languages, ANY variable can secretly be null — and the program crashes the moment you touch it. Swift quarantines that danger: a plain String can never be nil. When a value might be absent, its type says so with a question mark: String? — an optional. It holds either a wrapped value or nil, and the compiler forces you to deal with both cases before using it.

You meet optionals immediately with dictionaries. A lookup can always miss — the key might not be there — so the subscript returns String? instead of String:

let capitals = ["France": "Paris", "Japan": "Tokyo"]
capitals["Japan"]    // Optional("Tokyo") — the key exists
capitals["Brazil"]   // nil — no such key, but NO crash

Four ways to get the value out:

// 1. if let — run code only when there IS a value (safe)
if let city = capitals["Japan"] {
    print("got \(city)")
}

// 2. guard let — handle the nil case and exit early (safe)
guard let city = capitals["France"] else {
    return
}
// from here on, city is a plain String

// 3. ?? — take the value, or a fallback on nil (safe)
let city = capitals["Brazil"] ?? "unknown"

// 4. ! — force-unwrap: take the value, CRASH on nil (dangerous)
let city = capitals["Japan"]!   // fine: the key exists

if let unwraps for one block; guard let unwraps for the whole rest of the function — its else must exit (e.g. return), which is why the code after it can use the value directly.


The trap:

Force-unwrapping a nil kills your program at runtime. This compiles without a single complaint:

let capitals = ["France": "Paris", "Japan": "Tokyo"]
let capital = capitals["Brazil"]!   // no "Brazil" key -> nil...

...and then dies the moment it runs:

Fatal error: Unexpectedly found nil while unwrapping an Optional value

Read that message carefully — you WILL see it again. The rule: reach for if let, guard let or ?? by default, and save ! for when you can prove the value exists.


Assignment
Follow the Coding Tutorial and practice all four: create let capitals = ["France": "Paris", "Japan": "Tokyo", "Italy": "Rome"], then

  • look up capitals["Japan"], store it in lookup, and print it via if let,
  • unwrap capitals["France"] with guard let france ... else { return } and print it after the block,
  • print capitals["Brazil"] with the fallback ?? "unknown",
  • print capitals["Italy"]! — safe here, because the key certainly exists.

The exact expected output is:

Japan -> Tokyo
France -> Paris
Brazil -> unknown
Italy -> Rome

Hint
guard let only compiles when its else block exits the function — put return inside it. The lines print with string interpolation, e.g. print("Japan -> \(city)").


STEP 1 OF 9
    Ctrl + Enter to check

    SWIFT · CONDITIONAL STATEMENTS

    Optionals And Unwrapping in Swift, explained

    What an optional is

    In many languages, any variable can secretly hold a null value, and the program fails when you use it. Swift makes absence part of the type. A plain String always holds a string. When a value might be missing, the type says so with a question mark: String?. This is an optional. It holds either a wrapped String or nil, and the compiler won't let you use it as a plain String until you have dealt with both cases.

    Dictionaries are where you meet optionals first. A lookup can miss, so the subscript returns the value type wrapped in an optional:

    let capitals = ["France": "Paris", "Japan": "Tokyo"]
    print(capitals["Japan"] as Any)    // Optional("Tokyo")
    print(capitals["Brazil"] as Any)   // nil
    

    A missing key is not an error. It produces nil. The compiler stops you from forgetting that possibility: let s: String = capitals["France"] is rejected with an error saying the value of optional type String? must be unwrapped, and calling capitals["France"].count is rejected for the same reason. Getting the value out is called unwrapping.

    Four ways to unwrap

    1. if let

    if let runs a block only when a value is present, and binds it to a new constant that exists only inside the block:

    if let city = capitals["Japan"] {
        print("got \(city)")   // city is a plain String here
    }
    

    If the lookup gives nil, the block is skipped and nothing crashes. You can attach an else block for the missing case.

    2. guard let

    guard let works the other way around. It states what must be true to continue, and handles the failure first. The unwrapped constant stays available after the guard, for the rest of the enclosing scope. The else block must leave that scope, for example with return, otherwise the compiler rejects the code:

    func describe(_ country: String) {
        let capitals = ["France": "Paris", "Japan": "Tokyo"]
        guard let city = capitals[country] else {
            print("unknown country")
            return
        }
        print(city)   // a plain String from here on
    }
    
    describe("France")   // Paris
    describe("Peru")     // unknown country
    

    3. The nil-coalescing operator ??

    a ?? b gives the value inside a when there is one, and b otherwise. It turns an optional into a plain value in one expression:

    let city = capitals["Brazil"] ?? "unknown"   // "unknown"
    

    4. Force unwrapping with !

    A ! after an optional takes the value out without any check:

    let city = capitals["Japan"]!   // "Tokyo"
    

    This is safe only when the value must exist. If it is nil, the program crashes at run time. Code such as capitals["Brazil"]! compiles without complaint and then stops the program with a fatal error when it runs. Use if let, guard let or ?? by default, and keep ! for values you can prove are there.

    The exercise, step by step

    1. Create the dictionary. capitals maps "France" to "Paris", "Japan" to "Tokyo" and "Italy" to "Rome". Swift infers the type [String: String] from the literal, so no annotation is needed.
    2. Store a lookup. let lookup = capitals["Japan"] doesn't hold a String. Its type is String?, a wrapped value or nil.
    3. Unwrap with if let. if let city = lookup { ... } binds city inside the braces. Inside, print("Japan -> \(city)") uses the plain string.
    4. Unwrap with guard let. guard let france = capitals["France"] else { return } exits if the key is missing. Because of that, france is a plain String on the lines after it.
    5. Use ?? inside the interpolation. The key "Brazil" isn't present, so capitals["Brazil"] ?? "unknown" supplies the fallback.
    6. Force-unwrap Italy. The key exists, so capitals["Italy"]! is safe here.
    let capitals = ["France": "Paris", "Japan": "Tokyo", "Italy": "Rome"]
    let lookup = capitals["Japan"]
    if let city = lookup {
        print("Japan -> \(city)")
    }
    guard let france = capitals["France"] else {
        return
    }
    print("France -> \(france)")
    print("Brazil -> \(capitals["Brazil"] ?? "unknown")")
    print("Italy -> \(capitals["Italy"]!)")
    

    The expected output is:

    Japan -> Tokyo
    France -> Paris
    Brazil -> unknown
    Italy -> Rome
    

    Inside string interpolation, the quotes of the dictionary keys and of the fallback are fine, because everything between \( and ) is ordinary Swift code.

    Common mistakes

    • Printing an optional directly. Interpolating or printing a String? shows Optional("Tokyo") rather than Tokyo. Unwrap first.
    • Forcing a value that may be missing. A ! on a missing key crashes the program. A typo in a key is enough.
    • Using the constant outside if let. The constant bound by if let exists only inside its braces. A guard let constant, in contrast, lives on after the statement.
    • A guard else that falls through. The block must end with return or another exit such as break or continue in a loop. Otherwise the code doesn't compile.
    • Using ?? with the wrong type. The fallback must match the wrapped type: capitals["Brazil"] ?? 0 is an error for a dictionary of strings.

    Key takeaways

    • T? means a value of type T or nil, and the compiler enforces the check.
    • A dictionary subscript always returns an optional, because the key may be missing.
    • if let unwraps for one block, guard let unwraps for the rest of the scope, ?? supplies a default, and ! crashes on nil.
    Keep going with SwiftAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free