Understanding map, flatMap, and compactMap in Swift
This article explains the differences between map, flatMap, and compactMap in Swift, covering the three overloads of flatMap, their behavior with sequences and optionals, the rationale behind renaming one overload to compactMap, and when to prefer optional chaining versus optional mapping.
First, recall the distinction between map and flattenMap : flattenMap flattens the result of the transformation before forming the final collection. The Swift standard library provides three flatMap overloads.
Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element] where S : Sequence
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]In Swift 4.1, compactMap was introduced as a renamed version of the third overload, with no functional change. Before diving into the rename, we review the various map and flatMap versions.
1. The most common Sequence.flatMap
let numbers = [1, 2, 3, 4]
let mapped = numbers.map { Array(repeating: $0, count: $0) }
// [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]
let flatMapped = numbers.flatMap { Array(repeating: $0, count: $0) }
// [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]The map call returns a two‑dimensional array because the closure returns an array, while flatMap flattens each returned array into a single one‑dimensional array.
Conceptually, s.flatMap(transform) is equivalent to Array(s.map(transform).joined()) .
2. Optional.flatMap
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?The only difference between map and flatMap for Optional lies in the return type of the transformation: map wraps the result in an extra Optional , while flatMap removes one level of Optional before wrapping.
let possibleNumber: Int? = Int("42")
let possibleSquare = possibleNumber.map { $0 * $0 }
let possibleURL: URL? = URL(string: "https://www.liulishuo.com")
let possibleHost = possibleURL.flatMap { $0.host }In the first example, map yields an Int? because the closure returns a non‑optional value; in the second, flatMap yields an String? because the closure returns an optional.
Optional chaining can achieve similar results, but it works only when the receiver itself supports the chained property or method. For example, Int("42")?.map { $0 * $0 } does not compile because Int? has no map method.
3. The Sequence.flatMap that was renamed
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]This overload flattens an optional result into an array, which is handy for “failable” mappings:
let possibleNumbers = ["1", "2", "three", "///4///", "5"]
let flatMapped: [Int] = possibleNumbers.flatMap { str in Int(str) }
// [1, 2, 5]If map were used instead, the result would be [Int?] containing nil values. Because the closure’s return type can be implicitly converted from Int to Int? , the compiler accepts the code, but using map would be clearer. To avoid such misuse, the overload was renamed to compactMap in Swift 4.1:
let flatMapped: [Int] = possibleNumbers.compactMap { str in Int(str) }4. Summary
FlatMap flattens the result of the transformation, whereas map does not.
Optional mapping vs. optional chaining differ in where the optional handling occurs and in expressiveness.
The Sequence.flatMap that returns an array from an optional was renamed to compactMap to discourage accidental misuse.
5. Review
Click to review 👉 Swift 4.1 New Feature 1: Conditional Conformance
Liulishuo Tech Team
Help everyone become a global citizen!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.