`map`, `filter`, `reduce`, and `compactMap` in Swift Explained


Picture the Monsters, Inc. scare floor: hundreds of doors on a conveyor belt, each needing to be checked, sorted, and processed. You could walk through them one by one with a for loop — or you could let a machine handle each door in a single pass. That machine is a higher-order function.

You’ll learn how map, filter, reduce, and compactMap work, when to use each one, and how they combine to transform collections with minimal code. We won’t cover Combine or reactive programming — those build on these concepts in later posts.

What You’ll Learn

What Are Higher-Order Functions?

A higher-order function is a function that takes another function as a parameter. Instead of writing a for loop to process each item, you pass a closure describing what to do with each element, and the higher-order function handles the how.

Think of it like Remy’s kitchen at Gusteau’s restaurant: you (the chef) tell the conveyor belt what to do with each dish — “add salt,” “taste-test this,” “throw out anything burnt” — and the belt handles running each dish through your instructions.

Tip: These functions don’t mutate the original array. They always return a new collection with the results.

map — Transform Every Element

map takes a closure, applies it to every element in an array, and returns a new array with the transformed results.

Apple Docs: map(_:) — Swift Standard Library

let movies = ["Toy Story", "Finding Nemo", "Up", "Coco"]
let uppercased = movies.map { movie in
    movie.uppercased()
}
print(uppercased)
["TOY STORY", "FINDING NEMO", "UP", "COCO"]

Every string in the original array gets transformed to uppercase. The original movies array stays unchanged — map creates a new array.

You can also use the shorthand $0 for the closure parameter:

let lengths = movies.map { $0.count }
print(lengths)
[9, 11, 2, 4]

Here map transforms each movie title into its character count. The input is [String], the output is [Int]map can change the element type.

filter — Keep Only What Matches

filter takes a closure that returns true or false for each element. Only elements where the closure returns true make it into the new array.

Apple Docs: filter(_:) — Swift Standard Library

let scores = [9.8, 6.5, 8.1, 7.0, 9.2]
let topRated = scores.filter { score in
    score >= 8.0
}
print(topRated)
[9.8, 8.1, 9.2]

Only scores of 8.0 or above pass the filter. The closure acts as a gate — elements that don’t satisfy the condition are excluded.

let pixarCharacters = ["Woody", "Buzz", "Rex", "Bo Peep", "Slinky"]
let longNames = pixarCharacters.filter { $0.count > 4 }
print(longNames)
["Woody", "Bo Peep", "Slinky"]

reduce — Combine Everything into One Value

reduce collapses an entire array into a single value. You provide an initial value and a closure that combines the running total with each element.

Apple Docs: reduce(\_:\_:) — Swift Standard Library

let ratings = [9.8, 8.1, 7.5, 9.2]
let total = ratings.reduce(0.0) { runningTotal, rating in
    runningTotal + rating
}
print("Total: \(total)")
Total: 34.6

The closure receives two parameters: the accumulated result so far (runningTotal, starting at 0.0) and the current element (rating). Each pass adds the current rating to the running total.

You can also use operator shorthand for simple operations:

let sum = ratings.reduce(0.0, +)
let average = sum / Double(ratings.count)
print("Average: \(average)")
Average: 8.65

The + operator is itself a function that takes two numbers and returns their sum — perfect as a reduce closure.

Building Strings with reduce

reduce isn’t limited to numbers. Here’s how to build a comma-separated list:

let cast = ["Woody", "Buzz", "Jessie"]
let credits = cast.reduce("") { result, name in
    result.isEmpty ? name : "\(result), \(name)"
}
print("Cast: \(credits)")
Cast: Woody, Buzz, Jessie

compactMap — Transform and Remove nil

compactMap works like map, but it also removes any nil values from the result. This is essential when your transformation might fail for some elements.

Apple Docs: compactMap(_:) — Swift Standard Library

let input = ["9.8", "hello", "8.1", "nope", "7.5"]
let validScores = input.compactMap { text in
    Double(text)
}
print(validScores)
[9.8, 8.1, 7.5]

Double("hello") returns nil because “hello” isn’t a number. With regular map, you’d get [Optional(9.8), nil, Optional(8.1), nil, Optional(7.5)]. With compactMap, the nil values are automatically stripped out and the optionals are unwrapped.

let movieYears: [String?] = ["1995", nil, "2003", nil, "2009"]
let years = movieYears.compactMap { $0 }
print(years)
["1995", "2003", "2009"]

When your closure is just { $0 }, compactMap simply filters out nil values from an array of optionals.

Chaining Functions Together

The real power emerges when you chain these functions. Each function returns a new array, so you can call the next one immediately:

struct PixarMovie {
    let title: String
    let rating: Double
}

let movies = [
    PixarMovie(title: "Toy Story", rating: 9.8),
    PixarMovie(title: "Cars 2", rating: 6.2),
    PixarMovie(title: "Up", rating: 9.2),
    PixarMovie(title: "Brave", rating: 7.1),
    PixarMovie(title: "Coco", rating: 9.5)
]

let topTitles = movies
    .filter { $0.rating >= 8.0 }
    .map { $0.title }
    .sorted()
print(topTitles)
["Coco", "Toy Story", "Up"]

This reads almost like English: take the movies, keep those rated 8.0 or above, extract the titles, and sort them. Each step produces a new array that feeds into the next.

Common Mistakes

Using map When You Need compactMap

If your transformation can return nil, map gives you an array of optionals — which usually isn’t what you want:

let input = ["10", "abc", "20"]

// ❌ Returns [Optional(10), nil, Optional(20)]
let mapped = input.map { Int($0) }
print(mapped)
[Optional(10), nil, Optional(20)]
// ✅ Returns [10, 20] — nil values removed, optionals unwrapped
let compactMapped = input.compactMap { Int($0) }
print(compactMapped)
[10, 20]

Use compactMap whenever the closure returns an optional and you want to discard the nil results.

Overusing reduce for Simple Tasks

reduce is powerful but can be harder to read than alternatives. Don’t use it when a simpler method exists:

// ❌ Overcomplicated — reduce for counting
let names = ["Woody", "Buzz", "Jessie"]
let count = names.reduce(0) { total, _ in total + 1 }
// ✅ Just use .count
let count = names.count
print(count)
3

Similarly, use joined(separator:) instead of reduce for concatenating strings, and .min() / .max() instead of reduce for finding extremes.

What’s Next?

  • map transforms every element: [A] → [B]
  • filter keeps elements that match a condition: [A] → [A] (subset)
  • reduce collapses a collection into a single value: [A] → B
  • compactMap transforms and removes nil: [A] → [B] (non-optional)
  • Chain them together for expressive, readable data pipelines

Up next: Value Types vs Reference Types — understanding the difference between structs and classes at a deeper level, and why Swift favors value types.