Skip to content
Theory · Closures · lesson 5 of 8

compactMap and optionals

Back to the cook at the conveyor belt. This time gift boxes ride along the belt: some hold something, others are empty. The recipe card tells the cook: open the box, put what is inside on the tray, and throw the empty box away. In the end the tray holds only ingredients, with no boxes and no gaps.

In Swift such boxes appear when a closure returns an optional. As you already know, Int("abc") returns an optional: the text may hold no number. If you turn strings into numbers with map, you get an array of optionals — boxes, some of them empty:

SWIFT
let inputs = ["10", "abc", "25", ""]
print(type(of: inputs.map { Int($0) })) // Array<Optional<Int>>
let numbers = inputs.compactMap { Int($0) }
print(numbers) // [10, 25]
print(numbers.reduce(0, +)) // 35

Let's go through it line by line:

  1. map { Int($0) } gives an array of optionals, of type [Int?]. The full name of this type is Array<Optional<Int>>, which is what type(of:) printed. You cannot add up such elements with reduce(0, +): the compiler demands that each value be unwrapped.
  2. compactMap opens each box, throws away the empty ones and keeps the contents. "abc" and the empty string held no number, so numbers is [10, 25] of type [Int].
  3. reduce adds up the remaining numbers: 35.

How the result differs from map

compactMap calls the closure for each element, like map. But it skips an empty result and unwraps the value from a full one. So its array differs in two ways:

  • it can be shorter than the original: the four strings of inputs gave two numbers;
  • it holds no optionals: the type of numbers is [Int], and these numbers can be added up and sorted.

In the name compactMap, the word compact means "packed tight": the gaps are thrown away, and the values stand close together.

Here the belt picture is not exact. In the example, the belt carries plain strings, not boxes. The closure itself makes the box: Int($0) returns an optional, and compactMap opens it right away. The original array inputs stays the same, as it does after map.

Not only numbers from strings

Int(…) is not the only thing that returns an optional. A dictionary lookup gives a box too: the key may be missing. Here is how compactMap collects the prices of an order from a menu and skips the dishes the menu does not have:

SWIFT
let menu = ["tea": 50, "cake": 120]
let order = ["tea", "pizza", "cake"]
print(order.compactMap { menu[$0] }) // [50, 120]
print(order.compactMap { menu[$0] }.reduce(0, +)) // 170

Let's go through the example:

  1. menu[$0] looks the dish up in the menu and returns an Int?. The menu has no key "pizza", so the pizza gives nil.
  2. compactMap throws this nil away and keeps the prices of the tea and the cake: [50, 120].
  3. compactMap returns a plain array, so you call reduce right on the result, as in a chain of filter and map. The order costs 170.

Throw away or replace

Sometimes the array must keep its length: each input string should get its own number. Then you do not throw the empty box away but replace it with a fallback value through ??. inputs.map { Int($0) ?? 0 } gives [10, 0, 25, 0]: four strings, four numbers.

Interviewers like to ask this: how does compactMap differ from map? The answer:

  • map returns an array of the same length; if the closure returns an optional, you get an array of optionals [Int?];
  • compactMap throws away nil, unwraps the other values and returns an array without optionals [Int], sometimes shorter than the original.

Common mistake

The strings were turned into numbers with map and added up right away:

SWIFT
let sum = inputs.map { Int($0) }.reduce(0, +) // ✗
print(sum)
TEXT
error: value of optional type 'Int?' must be unwrapped to a value of type 'Int'

The program does not compile: map gave an array [Int?], and + cannot add up closed boxes. Under the error, the compiler suggests ?? or !. With ?? 0 the sum comes out right, but with ! the program crashes on the string "abc". Replace map with compactMap, and reduce adds up only the numbers: 35.

Key points

  • compactMap calls the closure for each element, throws away nil and unwraps the other values.
  • map with a closure that returns an optional gives an array [Int?], while compactMap gives an array [Int] that reduce can add up.
  • The array after compactMap can be shorter than the original; to keep the length, replace an empty value through ??.