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
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,$1and 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
| Method | Does | Example result |
|---|---|---|
map | Transforms every item | [1, 2].map { $0 * 10 } gives [10, 20] |
filter | Keeps items where the closure is true | [1, 2, 3].filter { $0 > 1 } gives [2, 3] |
reduce | Combines items into one value | [1, 2, 3].reduce(0, +) gives 6 |
compactMap | Transforms and drops nils | ["1", "x"].compactMap { Int($0) } gives [1] |
forEach | Runs code for each item | |
contains(where:), first(where:), allSatisfy | Questions 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.
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:
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:
var total = 10
let live = { print(total) }
let frozen = { [total] in print(total) }
total = 20
live() // 20
frozen() // 10Capture 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.
Transforming a shopping list
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 })["Notebook", "Backpack", "Pen"]
39.690000000000005
Backpack
trueLine-by-line explanation
- Lines 1 to 5A struct groups each item's data; the structs lesson covers them fully.
- Line 14filter keeps only items in stock.
- Line 15map turns items into their names.
- 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.
compactMap and chaining
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)[12, 7, 30]
420
12Line-by-line explanation
- Line 3Int($0) returns nil for bad text, and compactMap drops those nils.
- Lines 6 to 9Keep even numbers (12, 30), multiply by 10 (120, 300) and add them up.
- Line 12first(where:) returns an optional: the first match or nil.
Capturing values
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())1 2 3
5 10Line-by-line explanation
- Line 1The function returns a closure of type () -> Int.
- Lines 3 to 6The closure captures total and step, and keeps them between calls.
- Lines 11 to 12Each counter has its own captured total.
Common Swift Closures Mistakes
Using $0 with a long closure
let sorted = people.sorted { $0.lastName == $1.lastName ? $0.firstName < $1.firstName : $0.lastName < $1.lastName }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
var names: [String] = []
users.forEach { names.append($0.name) }let names = users.map { $0.name }map when you need to drop nils
let ages = ["30", "x"].map { Int($0) } // [Optional(30), nil]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.
Complete the code so every price is doubled.
let doubled = prices. { $0 * 2 }
What does this print?
print([1, 2, 3, 4].filter { $0 % 2 == 0 })
What does this print?
print([2, 3, 4].reduce(1, *))
What does $0 mean inside a closure?
When must a closure parameter be marked @escaping?
Swift Closures Quiz
5 questions to check what you learned.
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
What does
compactMapdo?- Sorts the array
- Removes duplicates
- Combines items into one value
- Transforms each item and removes the nil results
Do map and filter change the original array?
- Yes
- No, they return new arrays
- Only var arrays
- Only filter does
What does
xs.sorted(by: >)do?- Sorts smallest first
- Reverses the array
- Removes items greater than 0
- Returns a new array sorted largest first
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