EduFlow

Section 1 · Swift the Language

Optionals Without Crashes

16 min3 min readSpot the bug

Lesson at a glance

Read optional types, unwrap them safely with if let, guard let and ??, and explain why a force unwrap is a crash waiting to happen.

  1. 1What an optional is
  2. 2if let: use the value in one branch
  3. 3guard let: exit early, keep the happy path flat
  4. 4?? and optional chaining
  5. 5The force unwrap

No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.

The user taps Add Expense, types “12.50” into the amount field, and taps Save. Your code receives a String. Turning it into a number can fail: the field might be empty, or the user might have typed “twelve”. Many languages hand you a NaN, a zero or an exception for that. Swift hands you an optional, and makes you deal with the missing case before the code compiles.

What an optional is

Swift
import Foundation

let typed = "12.50"
let amount = Decimal(string: typed)   // amount is Decimal?, not Decimal

The question mark in Decimal? means “a Decimal, or nothing”. The “nothing” is spelled nil. Under the hood an optional is an enum with two cases, .some(value) and .none, which is why you can picture it as a box that is either full or empty.

Decimal(string:) returns a Decimal optional. For "12.50" the box holds 12.5; for an empty string the box is empty, which is nil. Unwrapping opens the box. Decimal(string:) .some(12.5) "12.50" nil "" or "twelve" unwrap handle
An optional is a box that is either full or empty

An optional is not the value inside it. Try amount + 5 and the compiler stops you with “value of optional type ‘Decimal?’ must be unwrapped to a value of type ‘Decimal’”. That error is the feature. Every place a value might be missing is marked in the type, and Swift won’t let you forget it.

if let: use the value in one branch

if let checks the box and, when it’s full, gives you the value under a new non-optional name:

Swift
if let amount = Decimal(string: typed) {
    print("Adding \(amount)")          // amount is a Decimal here
} else {
    print("That isn't a number")
}

When the unwrapped name matches an existing optional, Swift 5.7 and later let you drop the right-hand side: if let amount { … }. The new amount exists only inside the braces, which is perfect when the value matters for one small branch.

guard let: exit early, keep the happy path flat

Most of the time, a missing value means “stop here”. guard let says exactly that. Here is the function Pocket Budget uses to validate the amount field:

AmountParser.swift
import Foundation

func parseAmount(_ text: String) -> Decimal? {
    let trimmed = text.trimmingCharacters(in: .whitespaces)
    guard let amount = Decimal(string: trimmed), amount > 0 else {
        return nil
    }
    return amount
}

print(parseAmount("12.50") as Any)   // Optional(12.5)
print(parseAmount("") as Any)        // nil
print(parseAmount("-3") as Any)      // nil

The else block must leave the current scope with return, throw, break or continue, and the compiler enforces it. In exchange, amount stays available as a plain Decimal for the rest of the function. The comma adds a second condition, so one guard rejects both “not a number” and “zero or negative”. Code written this way reads top to bottom: checks first, real work after, no pyramid of nested ifs.

?? and optional chaining

Sometimes a missing value has an obvious default. The nil-coalescing operator ?? supplies it:

Swift
let note: String? = nil
let label = note ?? "No note"        // "No note"
let noteLength = note?.count ?? 0    // 0

note?.count is optional chaining: if note is nil, the whole expression becomes nil instead of crashing, and ?? turns that into 0. Chains can be long (expense?.category?.name), and they stop at the first nil.

Optionals are also a design decision, not only something functions hand you. In Pocket Budget an expense always has a title and an amount, so those are plain String and Decimal. A note is genuinely optional, so it’s String?. Resist making things optional “to be safe”: every ? you add is a question every reader of that value has to answer. Make a property optional only when “no value” is a real, meaningful state.

The force unwrap

There is one more way to open the box: !.

Swift
let amount = Decimal(string: typed)!

If typed is “12.50”, this works. If the field is empty, your app dies with “Fatal error: Unexpectedly found nil while unwrapping an Optional value”. There’s no error message for the user and no chance to recover.

! has legitimate uses, such as URL(string: "https://example.com")! built from a literal you wrote yourself. There, nil would mean a typo in your code, and crashing during development is the fastest way to find it. Be ready to defend every ! in code review.

With optionals in hand you can handle one missing value safely. Next you’ll store many values at once in arrays, dictionaries and sets, and steer through them with loops and switch.

Key takeaways

  • An optional such as Decimal? either holds a value or holds nil, and Swift won’t let you use it as a plain value until you unwrap it.
  • Use guard let to unwrap at the top of a function and exit early, so the rest of the function works with a real value.
  • Use if let when you only need the value inside one branch, and ?? when a sensible default exists.
  • A force unwrap (!) crashes the app the moment the value is nil; treat every ! as a claim you must be able to prove.

Further reading

Check your understanding

4 questions

Question 1 of 4

let note: String? = nil. What does note?.count ?? 0 evaluate to?

Question 2 of 4

Inside a function, after guard let amount = parseAmount(text) else { return }, what is true about amount on the next line?

Question 3 of 4

When is a force unwrap like URL(string: "https://example.com")! reasonable?

Question 4 of 4

Which line compiles when limit is an Int??

Practice

Spot the bug

Find the crash

This early version of Pocket Budget’s add-expense logic works in every demo, then crashes for a real user. Select the line that causes the crash.

Select the line(s) you think are wrong, then check.