What do nested optionals and nil assignments do: Int??, dictionaries of optionals, optional chaining and Int(String)?

27.5.4 · L3 · Interview

Predict first

What does this print?

let a: Int?? = .some(nil)
let b: Int?? = nil
print(a == nil, b == nil, a as Any, b as Any)
var d: [String: Int?] = ["x": 1]
d["x"] = nil
print(d.count)
d["y"] = .some(nil)
print(d.count, d["y"] as Any, d["z"] as Any)
d.updateValue(nil, forKey: "w")
print(d.keys.sorted())
let maybe: [Int?] = [nil, 1, nil, 2]
print(maybe.compactMap { $0 }, maybe.count, maybe.map { $0 ?? -1 })
print(Int("12") as Any, Int("1.5") as Any, Int(" 3") as Any, Int("+4") as Any)
let words = ["1", "x", "3"]
print(words.map { Int($0) }, words.compactMap { Int($0) })
var o: Int? = 5
o = nil
o? += 1
print(o as Any)
let arr: [Int]? = []
print(arr?.first as Any, arr?.isEmpty as Any, arr?.count ?? 0)
  • false true Optional(nil) nil / 0 / 1 Optional(nil) nil / ["w", "y"] / [1, 2] 4 [-1, 1, -1, 2] / Optional(12) nil nil Optional(4) / [Optional(1), nil, Optional(3)] [1, 3] / nil / nil Optional(true) 0
  • true true nil nil / 1 / 1 nil nil / ["y"] / [1, 2] 4 [-1, 1, -1, 2] / Optional(12) Optional(1) Optional(3) Optional(4) / [1, nil, 3] [1, 3] / Optional(1) / nil true 0
  • false false Optional(nil) nil / 0 / 0 nil nil / ["w"] / [1, 2] 2 [1, 2] / Optional(12) nil nil Optional(4) / [1, 3] [1, 3] / nil / nil Optional(true) 0

Mental model. A box inside a box: an empty outer box is different from an outer box that holds an empty inner box.

Int?? is an optional wrapping an optional. .some(nil) is not nil (a == nil is false), while a plain nil is. In a [String: Int?] the subscript returns Int??: d["x"] = nil removes the key (count 0), d["y"] = .some(nil) or updateValue(nil, forKey:) stores a nil value, and reading a missing key gives the outer nil while reading a stored nil gives Optional(nil).

Optional chaining adds one optional layer to the result: arr?.isEmpty is Optional(true), arr?.first is nil for an empty array, and o? += 1 on nil silently does nothing. Int("1.5") and Int(" 3") are nil (no trimming, no truncating) while Int("+4") is 4. map { Int($0) } keeps the nils; compactMap drops them.

Nil assignment removes, .some(nil) stores, chaining wraps once more.

Common traps

  • Using = nil to store a nil in a dictionary of optionals. It removes the key.
  • Expecting Int(" 3") to trim. It is nil.

They might follow up with

  • How do you flatten Int?? flatMap { $0 } or try?-style flattening (Q15).

Related questions

  • 1.2.1 What is an Optional in Swift, and what is it internally?
  • 1.2.6 How does optional chaining (a?.b?.c) actually work?
  • 1.2.7 What is flatMap vs map on Optional? What about Optional.map vs array map?
  • 1.2.2 Why can switch on an Optional use .none / .some and also nil?

← Why do count, unicodeScalars, utf16 and utf8 give different numbers…   In what order does switch try its cases, and what do where… →