---
title: Optionals
source: 'https://academia.sh/en/courses/swift/optionals'
course: 'The Swift Language'
language: en
updated: '2026-10-21T05:03:13+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Shows that an optional is an enumeration and is not the same type as the plain value'
    - 'Measures that the transforming closure is never evaluated when map is called on nil'
    - 'Reads the layer difference between map and flatMap from the output'
    - 'Measures that a filled outer layer makes the transformer run in nested layers'
---

# Optionals

Measuring how absence is expressed in the type, why a transforming closure never runs on nil, and where absence stops in nested layers.

The previous lesson measured closures and capture lists. A closure's body runs where it is called, not where it is written, and a closure that is never called never has its body evaluated. This lesson asks the same question through a method that takes a closure as a parameter and sometimes never calls it. That method belongs to the most heavily used part of the type system: the type that carries absence as a value.

Most languages express absence with a special value, such as a null pointer or a magic number, and leave it to the programmer to check for that value everywhere. Swift writes absence into the type itself. An **optional** is a box that either holds a value or holds nothing, and it is written `Int?`. An `Int` is always an integer; an `Int?` may or may not be one. The compiler does not let the two types stand in for each other, so the check for absence cannot be forgotten by accident; skipping it takes a force unwrap, written on purpose.

The course's measuring axis does not change in this lesson either: **when is this expression evaluated?** There are four moments: immediately, on first use, on every use, never. The `Probe` class, which counts by the rule, is given below exactly as it is. This lesson concentrates on the fourth moment: an expression that is never evaluated.

## The Core

Every lesson uses the same core. `Probe` counts how many times an expression is evaluated (`eval`) and how many times it is used (`use`); the `moment` property derives the moment from those two numbers. In the binding measurement of the opening lesson, the distribution over eight expressions was 2/1/3/2: two immediately, one on first use, three on every use, two never. In this lesson we measure only our own slice.

```swift
// Probe.swift — records when an expression is evaluated
final class Probe {
    let label: String
    private(set) var evaluations = 0
    private(set) var uses = 0
    private var evaluatedBeforeUse = false

    init(_ label: String) {
        self.label = label
    }

    func eval<T>(_ value: T) -> T {
        evaluations += 1
        if uses == 0 { evaluatedBeforeUse = true }
        return value
    }

    func use<T>(_ access: () -> T) -> T {
        uses += 1
        return access()
    }

    var moment: String {
        if evaluations == 0 { return "never" }
        if evaluations > 1 { return "on-every-use" }
        return evaluatedBeforeUse ? "immediately" : "on-first-use"
    }

    func report() {
        print("\(label): \(evaluations) evaluations, \(uses) uses -> \(moment)")
    }
}

func tally(_ probes: [Probe]) {
    for moment in ["immediately", "on-first-use", "on-every-use", "never"] {
        let count = probes.filter { $0.moment == moment }.count
        print("\(moment): \(count)")
    }
}
```

## Absence as a Type

Before looking at what is inside an optional, we look at the optional itself first. `Int("42")` converts a string to an integer, and because the conversion can fail, the return type is `Int?`. If it succeeds the box is filled; in a call like `Int("x")` the box is empty and its value is `nil`.

```swift
// main.swift — an optional is an enumeration
func describe(_ value: Int?) -> String {
    switch value {
    case .some(let number):
        return "filled \(number)"
    case .none:
        return "empty"
    }
}

let parsed = Int("42")
let broken = Int("x")
print(type(of: parsed))
print(describe(parsed))
print(describe(broken))
print(parsed == .some(42), broken == .none)
print(broken == nil)
```

```
Optional<Int>
filled 42
empty
true true
true
```

The first line gives the name of the type: `Int?` is only shorthand, and the real type is `Optional<Int>`. `Optional` is an **enumeration** defined in the standard library, and it has two cases: `.some`, which carries a value, and `.none`, which carries nothing. That is why the `describe` function can open the box with a `switch`; each case gets its own branch, and in the `.some` branch the carried value is bound to the name `number`. The fourth line shows that the value `42` equals `.some(42)` and that the empty box equals `.none`; the fifth line shows that writing `nil` is shorthand for `.none`.

A second conclusion follows from the same output: `Int?` and `Int` are two separate types. The fragments below do not compile; each was first run in a temporary measured block, and the compiler rejected both. They appear here for explanation only.

```swift
let a: Int? = 5
let b = a + 1   // does not compile: Int? and Int cannot be added
let c: Int = nil   // does not compile: nil can only be assigned to an optional type
```

The addition on the first line sees the box itself, not the number inside it, and the compiler asks for the box to be opened. The assignment on the second line tries to write absence into an `Int`. The only place that can carry absence is one whose type is marked with `?`. How the box is opened is the subject of the next lesson; in this lesson we will measure two methods that work with the box without opening it.

## Where Absence Comes From

Optionals do not enter a program only through string conversion. The standard library returns an optional in many places where absence is ordinary, though not everywhere: an array index out of bounds does not produce `nil`, it stops the program. A key that is not in a dictionary has no value; an empty array has no first element. In these cases there is no need to expect a special error value or an exception: the return type already says "maybe".

```swift
// main.swift — the standard library reports absence with an optional
let ages = ["ada": 36, "alan": 41]
let names: [String] = []
print(ages["ada"], ages["grace"])
print(names.first, [3, 1, 2].first)
print(ages["ada"] == 36, ages["grace"] == nil)
```

```
Optional(36) nil
nil Optional(3)
true true
```

On the first line, the value of the existing key arrives as `Optional(36)`; in other words, even `36` is not a plain `Int` but a box with `36` inside. The missing key gives `nil`. The second line shows the same pattern for arrays: the `first` property of the empty array is `nil`, while the `first` property of the non-empty array returns a boxed `3`. On the third line, the box is compared with an unboxed number: the `==` operator automatically boxes the `36` on the right so that both sides are `Int?`. That is why `ages["ada"] == 36` compiles, while the `+` example above does not, because it needs the value inside the box.

These sources make one distinction visible: the reason for the absence is not written in the optional's type. `ages["grace"]` is empty because the key does not exist, and `Int("x")` is empty because the string is not a number, but both are the same `nil`. When the reason is needed, error handling comes into play; that topic arrives later in this course. When the reason does not matter, an optional is the tool with the least overhead.

## map on nil

Since `Optional` is a two-case box, a method that transforms the value inside the box behaves differently in the two cases. `map` takes a **transforming closure**. If the box is filled, the closure is called with the value inside and the result is put in a new box; if the box is empty, the closure is not called and the result is an empty box. We measure it with two boxes, one filled and one empty, each used twice, and the body of the transforming closure marked with `eval`.

```swift
// main.swift — map on nil does not call the transformer
let filled = Probe("map-filled")
let vacant = Probe("map-empty")
let present: Int? = Int("21")
let missing: Int? = Int("twenty one")

for _ in 1...2 {
    _ = filled.use { present.map { filled.eval($0 * 2) } }
    _ = vacant.use { missing.map { vacant.eval($0 * 2) } }
}
filled.report()
vacant.report()
```

```
map-filled: 2 evaluations, 2 uses -> on-every-use
map-empty: 0 evaluations, 2 uses -> never
```

On the filled box, `map` runs the transformer again every time it is called: two uses, two evaluations, the moment is **on every use**. Notice that the result is not stored anywhere; the expression inside `use` runs from scratch each time. On the empty box there are two uses but no evaluations, because `map` returns as soon as it sees the box is empty, without ever reaching the transformer. The expression `filled.eval($0 * 2)` or `vacant.eval($0 * 2)` written in the body stays in the source, but on the empty box it is never evaluated. The rule names this case **never**.

This measurement shows once more when a closure's body runs: a closure does not run where it is written, and the method that is expected to call it does not always call it. An error, a side effect or an expensive computation inside a transformer that is never called never happens. Code that relies on the transformer's side effect misses that side effect on an empty box.

**TS1.** This measurement is for `map` only; the behavior of other methods such as `filter`, `compactMap` or `forEach` on an empty box was not tried. Cases where the transformer is written as a pure computation rather than with a side effect were not measured separately either.

### Two maps in a Chain

If an empty box comes out of one `map` call empty, a second `map` added after it meets the same box. When the first link of the chain produces nothing, the later links find no value to work on. We measure this with two transformers; the body of each transformer is tied to its own `Probe`, and the chain is built from scratch on every use.

```swift
// main.swift — on an empty box every map in the chain is skipped
let stageOne = Probe("chain-first")
let stageTwo = Probe("chain-second")
let seed: Int? = Int("ten")

for _ in 1...2 {
    _ = stageOne.use { seed.map { stageOne.eval($0 + 1) }.map { stageTwo.eval($0 * 2) } }
    _ = stageTwo.use { seed.map { stageOne.eval($0 + 1) }.map { stageTwo.eval($0 * 2) } }
}
stageOne.report()
stageTwo.report()
```

```
chain-first: 0 evaluations, 2 uses -> never
chain-second: 0 evaluations, 2 uses -> never
```

Neither transformer was ever evaluated. The second not running might have been expected, since its own input is the result of the first; the first not running comes from the box being empty in the first place. The `seed` value is built with `Int("ten")`, so it is `nil`; the first `map` passes the empty box through without transforming it, and the second `map` receives the same empty box. That is why, in a long chain of transformers, an absence at the very start runs none of the later links.

**TS2.** In this chain only the starting value is empty; the case where a transformer in the middle of the chain returns `nil` is not in this measurement, and the behavior of the following `map` there was not measured separately.

## map and flatMap

The transformer itself can also return an optional. `Int($0)`, which converts a string to an integer, is exactly such an expression: its result is `Int?`. `map` puts the transformer's result as it is into a new box, so a box goes inside a box. `flatMap` flattens the box the transformer returns out to the outside. The difference shows with three inputs: a valid string, an invalid string and an empty box.

```swift
// main.swift — map produces nested layers, flatMap flattens
let inputs: [String?] = ["7", "x", nil]
for input in inputs {
    let nested = input.map { Int($0) }
    let flat = input.flatMap { Int($0) }
    print(nested, flat)
}
```

```
Optional(Optional(7)) Optional(7)
Optional(nil) nil
nil nil
```

On the first line `map` gives a two-layer result and `flatMap` a single-layer one. The second line shows the real difference: `"x"` is an invalid string and the `map` result is `Optional(nil)`, meaning the outer box is filled and the inner box is empty; `flatMap` gives `nil` directly. On the third line the input itself is empty, so both methods never reach the transformer and `nil` comes back.

The second line is the ground for the next measurement. `Optional(nil)` and `nil` are different values, and both express absence; but one carries the absence one layer inside, the other at the very outside. Which layer it stops at decides whether the next method will call its transformer.

**TS3.** This output is for the `Int` conversion only; cases where the transformer converts to another type or throws an error were not tried. The `Optional(...)` notation in the output is the `print` format of this version; what is measured is the number of layers, not the format itself.

## The Bounding Measurement

At the start of the lesson we set a rule for `map`: on an empty box the transformer does not run. The rule is right, but which box counts as empty is a question of layers. The type `Int??` is a two-layer box. While the outer box is filled, the box inside can be empty; this case is written `.some(nil)`. Below are two values: one whose outer layer is filled but whose inside is empty, and one that is empty at the very outside. `map` is applied to both, and the transformer returns whether the value it received is `nil`.

```swift
// main.swift — if the outer layer is filled, map runs even when the absence is inside
let outer = Probe("outer-filled")
let hollow = Probe("outer-empty")
let inner: Int?? = .some(nil)
let empty: Int?? = nil

for _ in 1...2 {
    _ = outer.use { inner.map { value in outer.eval(value == nil) } }
    _ = hollow.use { empty.map { value in hollow.eval(value == nil) } }
}
outer.report()
hollow.report()
print(inner.map { $0 == nil })
print(empty.map { $0 == nil })
```

```
outer-filled: 2 evaluations, 2 uses -> on-every-use
outer-empty: 0 evaluations, 2 uses -> never
Optional(true)
nil
```

On the value whose outer layer is filled, the transformer ran and received `nil`: the result is `Optional(true)`, because the transformer returned the inner absence as `true`. On the value that is empty at the very outside, the transformer never ran. So for `map`, absence is only the outermost box being empty; a value that carries absence inside but is filled outside is, as far as `map` is concerned, filled.

This result narrows the area in which the lesson's rule holds. A transforming closure never being evaluated depends on the **outer layer** of the box being empty. When working with nested optionals, `map` does not carry absence "one layer up": it hands the absence in the inner layer to the transformer as a value. That is why the `Optional(nil)` result in the previous section is a filled box for a later `map` call. When the number of layers is to be reduced, `flatMap` is used.

**TS4.** This measurement is for a two-layer box; boxes with three or more layers were not tried. We observed that the transformer received `nil` through the `value == nil` comparison; no other way of observing it was tried.

## Summary

- `Int?` and `Int` are separate types; absence is carried inside the type, and the compiler makes the check mandatory.
- Optional is an enumeration: `.some` carries a value, `.none` (that is, `nil`) carries no value.
- On an empty box `map` never calls the transforming closure; on a filled box it runs it again on every call.
- When the transformer returns an optional, `map` adds a layer and `flatMap` flattens; `Optional(nil)` and `nil` are different values.
- In an `Int??` whose outer layer is filled, `map` runs the transformer even though absence is inside.
- This lesson's moment: **never**. On an empty box, the `map` transformer is never evaluated.

## Next Step

In this lesson we worked without opening the box: `map` and `flatMap` transformed the value but left the result in a box again. When a real value is needed, the box has to be opened. The next lesson measures the ways of opening it: when `if let` and `guard let` evaluate their right-hand side, at which candidate a `??` chain stops, and what optional chaining does to the rest of the chain when it sees `nil`.
