How does optional chaining (a?.b?.c) actually work?
Predict first
What does this print?
struct Address { var city: String? }
struct User { var address: Address? }
let u: User? = User(address: nil)
print(u?.address?.city?.count as Any)
- nil
- 0
- Crash
- Compile error
Mental model. It is a row of dominoes. The first nil one simply stops the rest from falling — no error, the whole expression is just nil.
Each ?. checks the value on its left. If it is nil, the entire expression short-circuits to nil and later subexpressions are never evaluated. The result type is the final type wrapped in an optional.
If the final call is throwing, the whole chain becomes throwing and you need try (or try?).
The chain stops at the first nil (address) and the whole expression becomes nil. No crash, and the result type is Int?.
Common traps
- Assuming side effects run. Any function call after the nil step is skipped entirely — including logging, counters, and mutations.
- Mixing
?and!in one chain. It compiles, but it means “I handled nil here but not there”; readers cannot tell which invariant you trust.
They might follow up with
- What is the type of
a?.bwhenbisInt?Int?— even ifais non-optional at the source. - Does optional chaining work on methods returning
Void? Yes, and the result isVoid?— which is whyif a?.doThing() != nilis a common (odd) idiom.
Related questions
- 1.2.3 if let vs guard let vs ?? vs force unwrap — when is each one OK?
- 1.2.7 What is flatMap vs map on Optional? What about Optional.map vs array map?
← What is the difference between if case, guard case, and switch on an… What is flatMap vs map on Optional? What about Optional.map vs array… →