Closures and higher-order functionsswift-6.4/ios-26
Lesson 1 / 5
Unit 6 · Closures & memory

Closures and higher-order functions

Coming from
Note

Unit 6 · Closures & memory. Closures are functions you can pass around and store. This lesson is the syntax and the everyday tools; the next lessons cover what closures capture and the memory consequences.

A closure is a self-contained block of code you can store in a variable, pass to a function, or return. Swift's collection APIs are built around them — map, filter, reduce — so reading and writing closures fluently is a daily skill, not a niche one.

Closure syntax, from full to terse

Swift lets you shrink a closure as the context makes its type clear. All four of these do the same thing:

let nums = [1, 2, 3, 4]

// 1. Full: explicit parameter and return type
let a = nums.map({ (n: Int) -> Int in n * 2 })

// 2. Inferred types — the context knows them
let b = nums.map({ n in n * 2 })

// 3. Trailing closure — move it outside the parentheses
let c = nums.map { n in n * 2 }

// 4. Shorthand argument names — $0 is the first parameter
let d = nums.map { $0 * 2 }

Prefer the terse forms when they stay readable. Trailing-closure syntax — the last closure argument moves outside the call's parentheses — is idiomatic Swift and reads especially well when the closure is the star of the call.

Tip

Name the parameter when it aids reading. $0 is great for tiny closures like { $0 * 2 }. Once a closure spans several lines or uses its argument repeatedly, { score in … } is clearer than a wall of $0s. Terseness is a means, not the goal.

The higher-order toolkit

A higher-order function takes (or returns) another function. These five cover most day-to-day transformations:

let scores = [42, 88, 60, 91, 55]

scores.map { $0 + 5 }                     // [47, 93, 65, 96, 60]  — transform each
scores.filter { $0 >= 60 }                // [88, 60, 91]          — keep matching
scores.reduce(0, +)                       // 336                   — combine into one
scores.sorted(by: >)                      // [91, 88, 60, 55, 42]  — reordered copy

let raw = ["3", "x", "7"]
raw.compactMap { Int($0) }                // [3, 7]  — map, dropping nils
  • map transforms every element, returning a new array of the results.
  • filter keeps the elements a predicate returns true for.
  • reduce folds the collection into a single value from an initial seed.
  • sorted returns a reordered copy (the original is untouched — value semantics).
  • compactMap is map that also discards nil results, unwrapping the rest.

Each returns a new value and never mutates the source, so they compose safely.

Chaining into a pipeline

Because each call returns a collection, you can chain them into a top-to-bottom pipeline:

let names = [("Ada", 90), ("Ben", 55), ("Cleo", 72)]

let topNames = names
    .filter { $0.1 >= 60 }        // keep passing
    .sorted { $0.1 > $1.1 }       // highest first
    .map { $0.0 }                 // project the name
// ["Ada", "Cleo"]

Read it as a sentence: keep the passing scores, sort by score descending, take the names. This declarative style states what you want and leaves the looping to the standard library.

Coming from Kotlin

Kotlin's collection operators (map, filter, fold, sortedByDescending) line up almost one-to-one; reduce(0, +) is Kotlin's fold(0) { a, b -> a + b }. Swift's $0 shorthand plays the role of Kotlin's implicit it. If you're comfortable with Kotlin sequences, this is a syntax reskin.

Your turn

Build a filter-then-map pipeline.

Filter and map with closures
Edit the code on the right, then run the hidden tests.

Knowledge check

Q: What is trailing-closure syntax, and when does it apply? When a function's last argument is a closure, you can write it outside the call's parentheses: nums.map { $0 * 2 } instead of nums.map({ $0 * 2 }). It's the idiomatic form and reads best when the closure is the main argument.

Q: How does compactMap differ from map? map returns one result per element. compactMap also drops any nil results and unwraps the rest, so ["3", "x"].compactMap { Int($0) } yields [3] — a non-optional array.

Solution.swiftSwift Testing
ConsoleReady · runs in a sandboxed Swift container
Press Run to compile against the hidden test suite.