Swift Closures
A closure is a block of code that you can store in a variable, pass to a function, or return from a function. Think of it as a function without a name. Closures are everywhere in Swift — you use them with arrays, timers, networking, and animations.
The Errand Note Analogy
┌───────────────────────────────────────────────────┐
│ Handing someone a note with instructions: │
│ │
│ "When the package arrives, │
│ sign for it and put it in the kitchen." │
│ │
│ You give someone a task to run later. │
│ A closure is that exact note — instructions │
│ saved to run at the right moment. │
└───────────────────────────────────────────────────┘
Function vs Closure Comparison
// Named function
func double(n: Int) -> Int {
return n * 2
}
// Same logic as a closure
let double = { (n: Int) -> Int in
return n * 2
}
print(double(5)) // 10
Both do the same job. The closure stores the logic in a variable instead of giving it a name with func.
Closure Syntax Breakdown
┌───────────────────────────────────────────────────┐
│ { (parameter: Type) -> ReturnType in │
│ code here │
│ } │
│ │
│ ① {} → wraps the closure body │
│ ② () → parameter list │
│ ③ -> → return type │
│ ④ in → separates signature from body │
└───────────────────────────────────────────────────┘
Passing a Closure to a Function
func runTask(action: () -> Void) {
print("Starting task...")
action()
print("Task done.")
}
runTask(action: {
print("Doing the work!")
})
// Starting task...
// Doing the work!
// Task done.
The function runTask accepts a closure as a parameter. The closure runs inside the function when action() is called.
Trailing Closure Syntax
runTask {
print("Doing the work!")
}
When a closure is the last argument, Swift lets you move it outside the parentheses. This is called a trailing closure and makes the code read more naturally.
Closures with Arrays – map, filter, sorted
map – Transform Every Element
let prices = [100, 200, 300]
let discounted = prices.map { price in price / 2 }
print(discounted) // [50, 100, 150]
filter – Keep Matching Elements
let scores = [45, 78, 92, 55, 88]
let passing = scores.filter { $0 >= 60 }
print(passing) // [78, 92, 88]
sorted – Reorder Elements
let names = ["Zara", "Amit", "Priya"]
let sorted = names.sorted { $0 < $1 }
print(sorted) // ["Amit", "Priya", "Zara"]
The $0 and $1 are shorthand for the first and second closure parameters. Swift fills them in automatically.
Capturing Values
func makeCounter() -> () -> Int {
var count = 0
return {
count += 1
return count
}
}
let counter = makeCounter()
print(counter()) // 1
print(counter()) // 2
print(counter()) // 3
The closure captures the count variable from its surrounding context. Even after makeCounter finishes, the closure remembers and updates count. This is called capturing.
Escaping Closures
var pendingActions: [() -> Void] = []
func schedule(action: @escaping () -> Void) {
pendingActions.append(action)
}
schedule { print("Task 1 runs later.") }
pendingActions[0]() // Task 1 runs later.
An @escaping closure lives beyond the function call — it is stored and used later. Without @escaping, Swift assumes the closure finishes before the function returns.
