`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?
map— Transform Every Elementfilter— Keep Only What Matchesreduce— Combine Everything into One ValuecompactMap— Transform and Removenil- Chaining Functions Together
- Common Mistakes
- What’s Next?
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?
maptransforms every element:[A] → [B]filterkeeps elements that match a condition:[A] → [A](subset)reducecollapses a collection into a single value:[A] → BcompactMaptransforms and removesnil:[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.