Swift map, filter, reduce: How to Transform Collections
map, filter, and reduce are three of the most useful higher-order functions in Swift. They let you transform, select, and combine values in collections like arrays without writing manual loops, which makes code shorter, clearer, and often easier to reason about.
Quick answer: Use map when you want to turn every element into a new value, filter when you want to keep only matching elements, and reduce when you want to combine all elements into one final result.
Difficulty: Beginner
Helpful to know first: You will understand this better if you already know basic Swift syntax, arrays, constants and variables, and how closures work at a simple level.
1. What Is map, filter, reduce?
In Swift, these are standard library methods available on sequences and collections. They are called higher-order functions because they take a closure as an argument.
- map transforms each element into a new element.
- filter keeps only the elements that match a condition.
- reduce combines all elements into one accumulated result.
They are often discussed together because they solve related collection-processing tasks. If you are deciding between them, think about the result you want:
- If you want another collection of the same length, start with map.
- If you want fewer elements based on a test, use filter.
- If you want one final value such as a sum, string, dictionary, or count, use reduce.
These methods do not usually mutate the original array. Instead, they return new values, which fits well with Swift's emphasis on clear and safe value handling.
2. Why map, filter, reduce Matters
These methods matter because collection processing is everywhere in Swift programs. You may need to convert model data into display text, remove invalid values, total prices in a shopping cart, or build lookup structures from arrays.
Compared with manual for loops, they often express intent more directly:
- map says “transform this data.”
- filter says “keep only these items.”
- reduce says “combine everything into one result.”
This makes code easier to read, especially when the operation itself is simple and the method name describes the goal.
These functions are not always better than a loop. If the logic is complex, has many side effects, or needs early exit, a regular loop may be clearer.
3. Basic Syntax or Core Idea
Using map
The closure passed to map receives one element and must return the transformed value.
let numbers = [1, 2, 3]
let doubled = numbers.map { number in
number * 2
}
Here, each number is multiplied by 2, so the result is a new array containing [2, 4, 6].
Using filter
The closure passed to filter must return a Boolean. true means keep the element, and false means discard it.
let numbers = [1, 2, 3, 4]
let evens = numbers.filter { number in
number % 2 == 0
}
This returns only the even numbers, so the result is [2, 4].
Using reduce
reduce takes an initial value and a closure. The closure receives the current accumulated value and the next element, then returns the updated accumulated value.
let numbers = [1, 2, 3, 4]
let sum = numbers.reduce(0) { partialResult, number in
partialResult + number
}
The initial value is 0. Swift adds each number to it, so the final result is 10.
4. Step-by-Step Examples
Example 1: Converting temperatures with map
This example converts Celsius values into Fahrenheit values. Every input value becomes one output value, so map is the right choice.
let celsiusValues = [0.0, 20.0, 30.0]
let fahrenheitValues = celsiusValues.map { celsius in
(celsius * 9 / 5) + 32
}
print(fahrenheitValues)
The output is an array of converted temperatures. The original array is unchanged.
Example 2: Keeping passing scores with filter
Here we want only scores that meet a passing threshold. Some elements stay, and some are removed, so filter fits naturally.
let scores = [42, 75, 90, 58, 81]
let passingScores = scores.filter { score in
score >= 60
}
print(passingScores)
The result contains only the scores that pass, which would be [75, 90, 81].
Example 3: Calculating a total with reduce
A shopping cart total is a classic reduce task. We want one final number from many prices.
let prices = [19.99, 5.50, 3.25]
let total = prices.reduce(0.0) { runningTotal, price in
runningTotal + price
}
print(total)
The closure adds each price to the running total until one final value remains.
Example 4: Chaining map, filter, and reduce
These methods become especially powerful when combined. In this example, we keep even numbers, square them, and then add them.
let numbers = [1, 2, 3, 4, 5, 6]
let result = numbers
.filter { number in
number % 2 == 0
}
.map { number in
number * number
}
.reduce(0) { sum, number in
sum + number
}
print(result)
The even numbers are [2, 4, 6], their squares are [4, 16, 36], and the final sum is 56.
5. Practical Use Cases
- Use map to convert model objects into display strings for user interfaces.
- Use filter to remove invalid, hidden, archived, or expired items from a list.
- Use reduce to calculate totals, counts, averages, or concatenated text.
- Use chained operations to clean and transform API data before displaying or storing it.
- Use map to extract one property from many objects, such as all usernames from an array of users.
- Use filter before expensive work so you process only relevant items.
- Use reduce to build dictionaries or grouped summaries from arrays.
6. Common Mistakes
Mistake 1: Using map when you really want filter
Beginners often use map when they want to remove elements. But map transforms every element and keeps the same number of elements.
Problem: This code returns Boolean values instead of returning only the passing scores, because map transforms each element rather than selecting matching ones.
let scores = [40, 72, 88]
let passingScores = scores.map { score in
score >= 60
}
Fix: Use filter when the goal is to keep only elements that match a condition.
let scores = [40, 72, 88]
let passingScores = scores.filter { score in
score >= 60
}
The corrected version works because filter removes elements that do not satisfy the condition.
Mistake 2: Forgetting that reduce needs the correct initial value
The initial value in reduce affects both the type and the result. A wrong starting value can produce incorrect output.
Problem: This code starts the sum at 10, so the final result is larger than expected even though the closure itself is correct.
let numbers = [1, 2, 3]
let sum = numbers.reduce(10) { partialResult, number in
partialResult + number
}
Fix: Choose an initial value that matches the identity of the operation, such as 0 for addition or an empty string for concatenation.
let numbers = [1, 2, 3]
let sum = numbers.reduce(0) { partialResult, number in
partialResult + number
}
The corrected version works because the accumulation starts from the proper neutral value.
Mistake 3: Forgetting to return the right type from map
The closure in map must return the transformed value for each element. If you return the wrong type, Swift reports a type mismatch.
Problem: This code tries to create strings from numbers but actually returns integers, which can lead to an error like Cannot convert value of type 'Int' to closure result type 'String'.
let numbers = [1, 2, 3]
let labels: [String] = numbers.map { number in
number * 2
}
Fix: Return the type you actually want each output element to have.
let numbers = [1, 2, 3]
let labels: [String] = numbers.map { number in
"Value: \(number * 2)"
}
The corrected version works because each element is transformed into a String, matching the declared result type.
Mistake 4: Using reduce for everything
reduce is powerful, but it can make simple transformations harder to read when used unnecessarily.
Problem: This code uses reduce to build an array of doubled values, which is possible but less clear than using map.
let numbers = [1, 2, 3]
let doubled = numbers.reduce([]) { partialResult, number in
partialResult + [number * 2]
}
Fix: Use the method that directly matches your intent. For one-to-one transformation, map is clearer.
let numbers = [1, 2, 3]
let doubled = numbers.map { number in
number * 2
}
The corrected version works because map communicates the transformation directly and more simply.
7. Best Practices
Choose the method that matches the result you want
A direct match between method and intent makes code easier to read. If you want one output per element, prefer map over forcing the logic into reduce.
// Less preferred for simple transformation
let uppercased1 = ["a", "b"].reduce([]) { result, item in
result + [item.uppercased()]
}
// Preferred
let uppercased2 = ["a", "b"].map { item in
item.uppercased()
}
The preferred version is easier to scan because the method name already tells the reader what is happening.
Keep closures short and focused
If a closure becomes long or hard to understand, readability drops quickly. A short closure makes chained operations much easier to maintain.
let names = [" alice ", " BOB "]
let cleanNames = names.map { name in
name.trimmingCharacters(in: .whitespaces).lowercased()
}
This closure is still compact enough to understand quickly. If it grows larger, consider extracting a helper function.
Use named helper functions when the intent is reusable
A named function can be clearer than repeating the same closure in multiple places.
func isAdult(age: Int) -> Bool {
age >= 18
}
let ages = [12, 19, 30]
let adults = ages.filter(isAdult)
This is useful when the rule has a meaningful name and might be reused in more than one place.
8. Limitations and Edge Cases
- map does not remove nil values from optionals. If you are transforming optional results and want to skip nil, compactMap is often the correct tool.
- filter preserves the original order of matching elements. It does not sort them.
- reduce can become hard to read when the accumulation logic is complex. In those cases, a loop may be easier to understand.
- Long chains of collection operations can create intermediate arrays. For typical app code this is usually fine, but performance-sensitive code may need closer inspection.
- These methods return new values rather than changing the original array in place, which can surprise beginners expecting mutation.
- If your closure has side effects, such as logging, writing files, or mutating outside state, the code may become harder to reason about even if it compiles.
A common comparison is map versus compactMap. map transforms every element and keeps optionals if you return them. compactMap transforms and removes nil results.
9. Practical Mini Project
Let’s build a small report from a list of quiz scores. We will use filter to keep passing scores, map to create labels, and reduce to calculate the total.
let scores = [45, 82, 67, 91, 58]
let passingScores = scores.filter { score in
score >= 60
}
let labels = passingScores.map { score in
"Passing score: \(score)"
}
let totalPassingScore = passingScores.reduce(0) { sum, score in
sum + score
}
print("Original scores: \(scores)")
print("Passing scores: \(passingScores)")
print("Labels: \(labels)")
print("Total of passing scores: \(totalPassingScore)")
This mini project shows a realistic workflow: first select the data you want, then transform it into a presentation-friendly format, and finally compute a summary value from it.
10. Key Points
- map transforms each element into a new value.
- filter keeps only elements that satisfy a condition.
- reduce combines all elements into one result.
- These methods usually return new values instead of mutating the original collection.
- Choose the method based on the shape of the result you want.
- Use chaining carefully so the code stays readable.
- Consider compactMap when you need to transform values and discard nil results.
11. Practice Exercise
Try this exercise to check your understanding.
- Start with an array of product prices: [12.0, 25.0, 8.0, 30.0, 15.0].
- Use filter to keep only prices greater than or equal to 15.
- Use map to convert those prices into strings like "$25.0".
- Use reduce to calculate the total of the filtered prices.
Expected output: an array of matching price labels and the total of the matching prices.
Hint: Do the filtering first, then transform and total the filtered results.
let prices = [12.0, 25.0, 8.0, 30.0, 15.0]
let filteredPrices = prices.filter { price in
price >= 15.0
}
let priceLabels = filteredPrices.map { price in
"$\(price)"
}
let total = filteredPrices.reduce(0.0) { sum, price in
sum + price
}
print(priceLabels)
print(total)
12. Final Summary
map, filter, and reduce are foundational Swift tools for working with collections. They let you express common tasks clearly: transform every value, keep only matching values, or combine everything into one final result. Once you understand the shape of each result, choosing the right one becomes much easier.
For beginners, the biggest improvement usually comes from learning to match the method to the problem instead of reaching for loops or forcing everything into reduce. For intermediate Swift developers, the next step is writing smaller, clearer closures and recognizing when related methods like compactMap or a plain for loop are the better fit.
A strong next step is to learn compactMap, flatMap, and closure shorthand syntax so you can read and write more expressive Swift collection code.