Skip to content
Theory · Closures · lesson 4 of 8

map, filter, reduce

Picture a cook at a conveyor belt of ingredients. You hand them a recipe card, and they apply it to each ingredient in turn. The cook knows how to walk along the belt; you only decide what to do with one ingredient.

With arrays, three tasks come up most often:

  • turn each element into something else;
  • keep only the matching elements;
  • combine all the elements into one value.

Each one can be solved with a for-in loop, but Swift has a ready-made method for each. The method walks the array itself, and you tell it what to do with an element through a closure:

SWIFT
let prices = [120, 45, 300, 80]
let doubled = prices.map { $0 * 2 }
let cheap = prices.filter { $0 < 100 }
let total = prices.reduce(0, +)
print(doubled) // [240, 90, 600, 160]
print(cheap) // [45, 80]
print(total) // 545

All three methods are higher-order functions: they take a function. Let's look at each one.

Three methods

map — transform each element

map calls the closure for each element and collects the results into a new array of the same length. prices.map { $0 * 2 } gives every price multiplied by two.

The element type may change along the way. The closure { "\($0) ₽" } would turn an array of numbers into an array of strings.

filter — keep the matching ones

The closure for filter returns a Bool. An element goes into the new array if the closure returned true, and is skipped if it returned false. prices.filter { $0 < 100 } keeps the prices below one hundred — [45, 80]. The order of the elements stays the same.

reduce — combine into one value

reduce(0, +) takes two arguments:

  • the initial value 0;
  • a function that combines the value accumulated so far with the next element; here it is the operator +.

Let's see how reduce walks through prices:

  1. It starts with 0.
  2. 0 + 120 gives 120.
  3. 120 + 45 gives 165.
  4. 165 + 300 gives 465.
  5. 465 + 80 gives 545 — and that is the result.

prices.reduce(0) { $0 + $1 } does the same: inside the closure $0 is the accumulated value and $1 is the next element.

Here the cook picture is not exact. The cook cuts the very ingredients lying on the belt. But map, filter and reduce do not touch the original array: prices stays the same after them.

A chain of calls

map and filter return a new array, so you can call the next method right on the result. The order in the chain matters:

SWIFT
let a = prices.filter { $0 < 100 }.map { $0 * 2 }
let b = prices.map { $0 * 2 }.filter { $0 < 100 }
print(a, b) // [90, 160] [90]

Let's go through both chains:

  1. In a, filter runs first and keeps [45, 80], and then map doubles them: [90, 160].
  2. In b, map first doubles all the prices: [240, 90, 600, 160]. Then filter keeps the ones below one hundred: [90].

A chain is read from left to right. Each next method works on the result of the previous one, not on the original array.

Interviewers like to ask this: how do map, filter and reduce differ? The answer:

  • map returns an array of the same length with every element transformed;
  • filter returns an array of the elements for which the closure returned true;
  • reduce returns one value combined from all the elements.

Common mistake

The goal was to remove the negative temperatures from the array, and the condition in filter was written for them:

SWIFT
let temps = [3, -2, 7, -5]
let positive = temps.filter { $0 < 0 } // ✗
print(positive) // [-2, -5]

There is no error at compile time or at run time, but positive holds exactly the negative numbers. filter keeps the elements for which the closure returned true; it does not remove them. Write the closure's condition for the elements that should stay: temps.filter { $0 > 0 } gives [3, 7].

Key points

  • map calls the closure for each element and returns a new array of the same length.
  • filter keeps the elements for which the closure returned true.
  • reduce(0, +) combines an array into one value, starting from 0.
  • None of these methods changes the original array, and in a chain each method works on the result of the previous one.