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.

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:

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:

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

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

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:

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

11. Practice Exercise

Try this exercise to check your understanding.

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.