Closures
Self-contained blocks of functionality that can be passed around like any other value.
2 min read
A closure is a self-contained block of code that can be passed around and used like any other value — assigned to a variable, passed as a function argument, or returned from a function. If you've used map, filter, or reduce from the collections lessons, you've already used closures without naming them.
Closure syntax
let multiply = { (a: Int, b: Int) -> Int in
return a * b
}
print(multiply(3, 4)) // 12The in keyword separates the closure's parameters and return type from its body — think of it as "given these inputs, here's what to do." A closure assigned to a variable like multiply behaves exactly like a function you'd call with ().
Passing closures to functions
This is where closures earn their keep — functions that accept a closure as a parameter let you customize behavior without rewriting the function itself:
func applyOperation(_ a: Int, _ b: Int, using operation: (Int, Int) -> Int) -> Int {
return operation(a, b)
}
let sum = applyOperation(5, 3, using: { x, y in x + y })
print(sum) // 8Array's map, filter, and reduce work exactly this way — each takes a closure describing what to do with each element:
let numbers = [1, 2, 3, 4]
let squared = numbers.map({ (n: Int) -> Int in
return n * n
})Trailing closure syntax and shorthand
Written out fully, closures are verbose enough that Swift provides several shortcuts, and idiomatic code uses all of them together. When a closure is the last argument to a function, it can move outside the parentheses as a trailing closure:
let squared = numbers.map { (n: Int) -> Int in
return n * n
}Since Swift can infer the closure's parameter and return types from context, they're usually omitted:
let squared = numbers.map { n in
return n * n
}For a single-expression closure, return can be dropped too, and Swift provides automatic shorthand argument names ($0, $1, ...) so you don't even need to name the parameter:
let squared = numbers.map { $0 * $0 }All four versions above do exactly the same thing — this last one is what you'll see most often in real Swift code once the closure's job is simple enough to be obvious at a glance.
Capturing values from the surrounding context
A closure automatically captures variables from the scope where it's defined, keeping them alive for as long as the closure exists:
func makeCounter() -> () -> Int {
var count = 0
return {
count += 1
return count
}
}
let counter = makeCounter()
print(counter()) // 1
print(counter()) // 2Each call to makeCounter() creates a fresh count variable, and the returned closure keeps a private reference to it — a pattern that comes up often for things like tracking state without a full class.