Swift flatMap vs compactMap: How to Transform Collections
flatMap and compactMap are two important Swift higher-order functions used to transform collection data. They look similar at first, but they solve different problems: one is for flattening nested results, and the other is for removing nil values while transforming. Understanding the difference helps you write cleaner, safer, and more expressive Swift code.
Quick answer: Use compactMap when your transformation might return nil and you want a result with only non-optional values. Use flatMap when each element produces a sequence or collection and you want the nested results flattened into a single collection.
Difficulty: Beginner
Helpful to know first: basic Swift syntax, arrays, optionals, closures, and how map transforms values in a collection.
1. What Are flatMap and compactMap?
Both methods are used on sequences such as arrays, but they produce different kinds of results.
- map transforms each element and keeps the same overall structure.
- compactMap transforms each element and discards any nil results.
- flatMap transforms each element into a sequence, then flattens one level of nesting.
- They are often confused because older Swift versions used flatMap in some optional-removal situations that now belong to compactMap.
For example, if you have an array of strings and want to convert them to integers, some conversions may fail and return nil. That is a natural job for compactMap. If you have an array of arrays and want one flat array, that is a natural job for flatMap.
2. Why flatMap and compactMap Matter
Real programs often deal with incomplete or nested data. User input may contain invalid values. File or API data may contain empty entries. Collections may contain other collections. These methods help you handle those cases clearly without writing verbose loops.
They matter because they let you:
- convert values while keeping code concise
- remove invalid or missing results safely
- flatten nested collections without manual loops
- express intent more clearly than custom filtering and appending code
If you choose the wrong method, your code may produce the wrong shape of data, such as an array of optionals instead of plain values, or a nested array when you expected a flat one.
3. Basic Syntax and Core Idea
Using compactMap
Use compactMap when the closure returns an optional. Swift keeps only the non-nil results.
let values = ["1", "2", "abc", "4"]
let numbers = values.compactMap { Int($0) }
This produces an array of integers. The string "abc" cannot become an Int, so its conversion returns nil and is dropped.
// Result: [1, 2, 4]
Using flatMap
Use flatMap when each element becomes a collection or sequence and you want one combined result.
let groups = [[1, 2], [3, 4], [5]]
let allNumbers = groups.flatMap { $0 }
This takes a nested array and flattens it by one level.
// Result: [1, 2, 3, 4, 5]
Swift flatMap vs compactMap
The key difference is in what the closure returns:
- If the closure returns an optional and you want to remove nil, use compactMap.
- If the closure returns a sequence or collection and you want to flatten one level, use flatMap.
4. Step-by-Step Examples
Example 1: Convert valid strings to integers with compactMap
This is one of the most common beginner examples because it shows both transformation and optional removal clearly.
let rawScores = ["10", "25", "oops", "42"]
let scores = rawScores.compactMap { Int($0) }
print(scores)
The output is a clean array of integers with the invalid string removed.
// [10, 25, 42]
This works because Int($0) returns Int?, and compactMap keeps only successful conversions.
Example 2: Flatten nested arrays with flatMap
Here each element is already an array. You want one array containing all inner values.
let weeklyTemps = [[21, 22], [19, 20], [23]]
let allTemps = weeklyTemps.flatMap { $0 }
print(allTemps)
The result is a single flat array.
// [21, 22, 19, 20, 23]
This is simpler than writing a loop and manually appending each inner array.
Example 3: Extract tags from articles with flatMap
This example is more realistic. Suppose each article has multiple tags, and you want all tags in one list.
struct Article {
let title: String
let tags: [String]
}
let articles = [
Article(title: "Swift Basics", tags: ["swift", "beginner"]),
Article(title: "Optionals", tags: ["swift", "optionals"])
]
let allTags = articles.flatMap { $0.tags }
print(allTags)
The output contains every tag from every article in one array.
// ["swift", "beginner", "swift", "optionals"]
This is a common pattern when collecting nested values from model data.
Example 4: Clean user input with compactMap
Suppose user input contains empty strings or invalid numbers. You can trim, validate, and keep only useful values.
let input = ["15", " ", "30", "abc"]
let cleanNumbers = input.compactMap { text in
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
return Int(trimmed)
}
print(cleanNumbers)
Only valid integers remain in the result.
// [15, 30]
This is a practical use of compactMap because invalid entries naturally become nil.
5. Practical Use Cases
- Parsing arrays of strings from forms, files, or APIs into numbers with compactMap.
- Removing missing values from optional properties collected from model objects.
- Flattening nested arrays such as grouped search results, menu sections, or batched responses with flatMap.
- Collecting child items from parent objects, such as tags from posts or permissions from user roles.
- Building a clean list of valid URLs, IDs, or dates from mixed raw input using compactMap.
- Combining one level of nested collection data before sorting, filtering, or counting.
6. Common Mistakes
Mistake 1: Using map when compactMap is needed
This happens when the transformation returns an optional but you still use map. The result becomes an array of optionals instead of plain values.
Problem: This code keeps failed conversions as nil, so the result type is [Int?] instead of [Int]. That often leads to extra unwrapping later.
let items = ["1", "x", "3"]
let numbers = items.map { Int($0) }
Fix: Use compactMap when failed transformations should be removed from the result.
let items = ["1", "x", "3"]
let numbers = items.compactMap { Int($0) }
The corrected version works because compactMap removes nil values and returns a plain integer array.
Mistake 2: Using compactMap when flatMap is needed
Some developers try to flatten nested arrays with compactMap because the names sound related. But compactMap does not flatten nested collections.
Problem: This code returns the same nested shape because there are no optional values to remove. It does not combine inner arrays into one array.
let groups = [[1, 2], [3, 4]]
let result = groups.compactMap { $0 }
Fix: Use flatMap when each element produces a collection and you want a single flattened result.
let groups = [[1, 2], [3, 4]]
let result = groups.flatMap { $0 }
The corrected version works because flatMap flattens one level of nested arrays.
Mistake 3: Expecting compactMap to keep array positions
compactMap removes elements that become nil. That means the result may be shorter and indexes may shift.
Problem: If later code depends on the original positions, removing invalid elements can break that assumption and produce incorrect logic.
let raw = ["100", "bad", "300"]
let values = raw.compactMap { Int($0) }
print(values[1])
Fix: Keep the optional values with map if position matters, or store original indexes alongside the transformed values.
let raw = ["100", "bad", "300"]
let values = raw.map { Int($0) }
print(values[1] as Int?)
The corrected version works because the array keeps the original number of elements, including failed conversions as nil.
Mistake 4: Expecting flatMap to flatten multiple nested levels automatically
flatMap flattens one level at a time. It does not completely flatten deeply nested data in a single call.
Problem: If your data is nested more than one level, one call to flatMap may still leave nested arrays in the result.
let deeplyNested = [[[1, 2]], [[3]]]
let partlyFlat = deeplyNested.flatMap { $0 }
Fix: Apply another flattening step if the result is still nested.
let deeplyNested = [[[1, 2]], [[3]]]
let fullyFlat = deeplyNested.flatMap { $0 }.flatMap { $0 }
The corrected version works because each flatMap call removes only one level of nesting.
7. Best Practices
Use compactMap when failure is normal and ignorable
If some values are expected to fail conversion and you simply want the valid results, compactMap is a clean choice.
let ids = ["10", "x", "30"]
let validIDs = ids.compactMap { Int($0) }
This keeps the code short and communicates that invalid values should be skipped.
Use flatMap only when flattening is the real goal
If your closure returns collections and you want one combined collection, flatMap expresses that clearly.
let folders = [["a.txt", "b.txt"], ["c.txt"]]
let files = folders.flatMap { $0 }
This is better than using a more confusing transformation that hides the flattening intent.
Choose map when you need to preserve shape
Sometimes removing values is not appropriate. If every input should correspond to one output position, map may be the better choice.
let rawValues = ["1", "bad", "3"]
let parsedValues = rawValues.map { Int($0) }
This preserves the relationship between source items and result positions.
Prefer readable closures over clever shorthand
Short closures are fine, but if validation or cleanup logic grows, a named closure parameter can make the intent easier to read.
let rawInput = [" 8 ", "nine", "10"]
let numbers = rawInput.compactMap { text in
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
return Int(trimmed)
}
This is easier to maintain than a dense one-line expression when the transformation becomes more complex.
8. Limitations and Edge Cases
- flatMap on sequences flattens one level only. Deeply nested structures need repeated flattening or a custom approach.
- compactMap removes failed values entirely, so the output count may be smaller than the input count.
- If you need to know why a conversion failed, compactMap may hide too much information because it only drops nil values.
- Older Swift explanations online may show flatMap where modern Swift uses compactMap. Be careful when reading outdated examples.
- If your closure returns the wrong type, Swift may report confusing type inference errors such as Cannot convert value of type messages. That usually means you chose the wrong transformation method.
- compactMap is not a replacement for validation with error reporting. It is best when silent skipping is acceptable.
9. Practical Mini Project
In this mini project, you will process a small set of order data. Each order stores item IDs as strings. Some IDs are invalid, and each order contains multiple items. The goal is to clean the IDs and then create one flat list of all valid item IDs.
struct Order {
let customer: String
let itemIDs: [String]
}
let orders = [
Order(customer: "Ava", itemIDs: ["101", "102", "x"]),
Order(customer: "Noah", itemIDs: ["205", " ", "206"]),
Order(customer: "Mia", itemIDs: ["300", "bad"])
]
let validItemIDs = orders.flatMap { order in
order.itemIDs.compactMap { rawID in
let trimmed = rawID.trimmingCharacters(in: .whitespacesAndNewlines)
return Int(trimmed)
}
}
print(validItemIDs)
This example combines both methods naturally:
- compactMap converts valid item ID strings into integers and removes invalid ones.
- flatMap combines each order's cleaned item IDs into one final array.
The result is:
// [101, 102, 205, 206, 300]
This is a realistic pattern for cleaning nested data from forms, files, or network responses.
10. Key Points
- compactMap transforms values and removes any nil results.
- flatMap transforms values into collections and flattens one level.
- Use map when you want to preserve the original structure and count.
- compactMap is ideal for parsing valid values from mixed input.
- flatMap is ideal for turning nested collections into one collection.
- These methods are similar in style but solve different problems.
- Outdated Swift examples may use flatMap where modern code should use compactMap.
11. Practice Exercise
Try this exercise to check your understanding.
- Create an array of arrays of strings representing test scores.
- Some strings should be valid integers and some should be invalid, such as "NA".
- Use compactMap to convert each inner array to valid integers only.
- Use flatMap to combine all valid scores into one array.
- Print the final array.
Expected output: one flat array containing only valid integer scores.
Hint: Clean each inner array first, then flatten the overall result.
let scoreGroups = [
["10", "20", "NA"],
["15", "oops", "25"],
["30", "40"]
]
let allScores = scoreGroups.flatMap { group in
group.compactMap { Int($0) }
}
print(allScores)
This solution works because each inner group is cleaned with compactMap, and then all cleaned groups are merged with flatMap.
12. Final Summary
flatMap and compactMap are closely related, but they are not interchangeable. compactMap is for transformations that may fail and return nil, while flatMap is for flattening one level of nested collections. Once you understand that difference, choosing the right one becomes much easier.
In Swift, these methods help you replace manual loops with code that is shorter and more expressive. They are especially useful when cleaning raw input, parsing values, and combining nested data structures. As a next step, compare them with map and filter so you can recognize exactly which transformation fits each problem.