How does optional chaining (a?.b?.c) actually work?

1.2.6 · L2 · Learning

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?.b when b is Int? Int? — even if a is non-optional at the source.
  • Does optional chaining work on methods returning Void? Yes, and the result is Void? — which is why if a?.doThing() != nil is 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… →