Swift Closures

Write functions as values with closures, shorten them with type inference, $0 and trailing closure syntax, and transform collections with map, filter, reduce, sorted and compactMap.

Lesson 11 of 43Intermediate18 min read

A closure is a block of code that you can store in a variable, pass to a function or return from one: a function without a name. You've already used one in the arrays lesson: removeAll { $0 == "tea" }. The part in braces is a closure that tells removeAll which items to remove.

Closures are everywhere in Swift. They describe how to sort, filter and transform collections, what happens when a button is tapped, and what to do when a network request finishes. Swift offers several shortcuts that make closures very compact, from inferred types to shorthand names like $0 and trailing closure syntax.

In this lesson you'll write closures in full and then shorten them step by step, use the essential collection methods map, filter, reduce, sorted and compactMap, chain them together, and see how closures capture values from their surroundings.

Closure syntax, from long to short

Swift
let numbers = [3, 1, 2]

// full form
let a = numbers.sorted(by: { (x: Int, y: Int) -> Bool in return x < y })
// types inferred
let b = numbers.sorted(by: { x, y in x < y })
// shorthand argument names
let c = numbers.sorted(by: { $0 < $1 })
// trailing closure
let d = numbers.sorted { $0 < $1 }
// an operator is itself a function
let e = numbers.sorted(by: <)
  • Parameters and the return type come first, then in, then the body.
  • When Swift knows the types, leave them out; a single-expression body returns its value without return.
  • $0, $1 and so on name the arguments by position.
  • If a closure is the last argument, it can go after the brackets; if it's the only argument, the brackets can go too.

map, filter and reduce

MethodDoesExample result
mapTransforms every item[1, 2].map { $0 * 10 } gives [10, 20]
filterKeeps items where the closure is true[1, 2, 3].filter { $0 > 1 } gives [2, 3]
reduceCombines items into one value[1, 2, 3].reduce(0, +) gives 6
compactMapTransforms and drops nils["1", "x"].compactMap { Int($0) } gives [1]
forEachRuns code for each item
contains(where:), first(where:), allSatisfyQuestions about items

These return new arrays and never change the original, so they can be chained: orders.filter { $0.paid }.map { $0.total }.reduce(0, +).

Function types and capturing

A closure's type is written like a function's: (Int) -> Int takes and returns an Int, () -> Void takes nothing and returns nothing.

Swift
let double: (Int) -> Int = { $0 * 2 }

func makeCounter() -> () -> Int {
    var count = 0
    return { count += 1; return count }
}

The closure returned by makeCounter captures count, keeping it alive and remembering its value between calls, even after makeCounter has finished.

Escaping closures

A closure passed to a function that runs it later, after the function has returned, such as a completion handler for a download, must be marked @escaping:

Swift
func load(completion: @escaping (String) -> Void) { ... }

Inside a class, an escaping closure that uses self can keep the object alive longer than expected; the memory lesson explains [weak self]. Modern Swift often replaces completion handlers with async functions, covered in the concurrency lesson.

Capture lists: freezing a value

A closure normally captures the variable itself, not a snapshot of its value, so it sees later changes. A capture list in square brackets, placed before the parameters, copies the value at the moment the closure is created:

Swift
var total = 10
let live = { print(total) }
let frozen = { [total] in print(total) }
total = 20
live()      // 20
frozen()    // 10

Capture lists are also where you write [weak self] to avoid keeping an object alive by accident, which the memory lesson covers.

Swift Closures Syntax

Closure forms

let f: (Int) -> Int = { x in x * 2 }
let g = { (a: Int, b: Int) -> Int in a + b }
xs.map { $0 * 2 }
xs.filter { $0 > 0 }
xs.reduce(0, +)
xs.sorted { $0.name < $1.name }
xs.compactMap { Int($0) }
xs.first(where: { $0 > 10 })
func run(_ action: @escaping () -> Void) { }

Swift Closures Summary

  • A closure is a function value written in braces: { (params) -> Type in body }.
  • Inferred types, implicit returns, $0 and trailing closures make closures short.
  • map transforms, filter keeps, reduce combines, compactMap drops nils; they return new arrays and chain well.
  • Closures capture variables from their surroundings and keep them alive.
  • Closures stored or run later must be marked @escaping.

Swift Closures Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Transforming a shopping list

Swift
struct Item {
    let name: String
    let price: Double
    let inStock: Bool
}

let items = [
    Item(name: "Notebook", price: 3.5, inStock: true),
    Item(name: "Lunch box", price: 8.75, inStock: false),
    Item(name: "Backpack", price: 34.99, inStock: true),
    Item(name: "Pen", price: 1.2, inStock: true)
]

let available = items.filter { $0.inStock }
print(available.map { $0.name })
print(available.map(\.price).reduce(0, +))
print(items.sorted { $0.price > $1.price }.first?.name ?? "")
print(items.contains { $0.price > 30 })
Output
["Notebook", "Backpack", "Pen"]
39.690000000000005
Backpack
true
Line-by-line explanation
  1. Lines 1 to 5A struct groups each item's data; the structs lesson covers them fully.
  2. Line 14filter keeps only items in stock.
  3. Line 15map turns items into their names.
  4. Line 16\.price is a key path, a short way to write { $0.price }; reduce adds the prices. The tiny rounding error is normal for Doubles.
Example 2

compactMap and chaining

Swift
let inputs = ["12", "abc", "7", "", "30"]

let numbers = inputs.compactMap { Int($0) }
print(numbers)

let result = numbers
    .filter { $0 % 2 == 0 }
    .map { $0 * 10 }
    .reduce(0, +)
print(result)

let firstBig = numbers.first(where: { $0 > 10 })
print(firstBig ?? -1)
Output
[12, 7, 30]
420
12
Line-by-line explanation
  1. Line 3Int($0) returns nil for bad text, and compactMap drops those nils.
  2. Lines 6 to 9Keep even numbers (12, 30), multiply by 10 (120, 300) and add them up.
  3. Line 12first(where:) returns an optional: the first match or nil.
Example 3

Capturing values

Swift
func makeCounter(step: Int) -> () -> Int {
    var total = 0
    return {
        total += step
        return total
    }
}

let byOne = makeCounter(step: 1)
let byFive = makeCounter(step: 5)
print(byOne(), byOne(), byOne())
print(byFive(), byFive())
Output
1 2 3
5 10
Line-by-line explanation
  1. Line 1The function returns a closure of type () -> Int.
  2. Lines 3 to 6The closure captures total and step, and keeps them between calls.
  3. Lines 11 to 12Each counter has its own captured total.

Common Swift Closures Mistakes

Using $0 with a long closure

Don't
let sorted = people.sorted { $0.lastName == $1.lastName ? $0.firstName < $1.firstName : $0.lastName < $1.lastName }
Do
let sorted = people.sorted { a, b in
    if a.lastName == b.lastName { return a.firstName < b.firstName }
    return a.lastName < b.lastName
}

Using forEach to build an array

Don't
var names: [String] = []
users.forEach { names.append($0.name) }
Do
let names = users.map { $0.name }

map when you need to drop nils

Don't
let ages = ["30", "x"].map { Int($0) }        // [Optional(30), nil]
Do
let ages = ["30", "x"].compactMap { Int($0) } // [30]

Swift Closures in Real Projects

Closures in Swift apps

SwiftUI is built on closures: a Button("Save") { save() } takes its action as a trailing closure, and views like List and ForEach take closures that build each row. Data code is full of map, filter and sorted pipelines that turn raw data into what a screen shows. Older APIs pass completion handler closures, while newer ones use async functions.

Swift Closures FAQ

What does $0 mean in Swift?

$0 is the shorthand name for a closure's first argument, $1 for the second, and so on. In numbers.filter { $0 > 10 }, $0 stands for each number in turn. Use it for short closures and give parameters real names when the closure gets longer.

What is a trailing closure in Swift?

When a closure is the last argument to a function, you can write it after the closing bracket: names.sorted { $0 < $1 } instead of names.sorted(by: { $0 < $1 }). If the closure is the only argument, the brackets can be left out entirely. SwiftUI relies on this style heavily.

What is the difference between map and compactMap?

map transforms every item and keeps the same number of items, including any nils it produces. compactMap transforms each item and throws away the nil results, so ["1", "x", "3"].compactMap { Int($0) } gives [1, 3] while map would give [Optional(1), nil, Optional(3)].

What does @escaping mean in Swift?

It marks a closure parameter that may be called after the function returns, for example stored in a property or run when a network request finishes. Closures are non-escaping by default, which lets the compiler make guarantees about them. The compiler tells you when @escaping is needed.

Are closures reference types in Swift?

Yes. Assigning a closure to another variable doesn't copy what it captured: both names refer to the same closure and share its captured variables. That's why two references to the closure returned by makeCounter() keep counting from the same number.

Swift Closures Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the code so every price is doubled.

let doubled = prices. { $0 * 2 }
Predict the outputeasy

What does this print?

print([1, 2, 3, 4].filter { $0 % 2 == 0 })
Predict the outputmedium

What does this print?

print([2, 3, 4].reduce(1, *))
Multiple choicemedium

What does $0 mean inside a closure?

Multiple choicehard

When must a closure parameter be marked @escaping?

Swift Closures Quiz

5 questions to check what you learned.

  1. What is a trailing closure?

    • A closure at the end of a file
    • A closure written after a function call's brackets when it's the last argument
    • A closure that returns nothing
    • An escaping closure
  2. What does compactMap do?

    • Sorts the array
    • Removes duplicates
    • Combines items into one value
    • Transforms each item and removes the nil results
  3. Do map and filter change the original array?

    • Yes
    • No, they return new arrays
    • Only var arrays
    • Only filter does
  4. What does xs.sorted(by: >) do?

    • Sorts smallest first
    • Reverses the array
    • Removes items greater than 0
    • Returns a new array sorted largest first
  5. What does it mean that a closure captures a variable?

    • It copies the variable once
    • It deletes the variable
    • It keeps a reference to the variable so it can read and change it later, even after the original scope ends
    • It makes the variable constant