Swift Closures

Closures are self-contained blocks of functionality that can be passed around and executed later, and they can capture variables from the scope where they were created.

What Is a Closure?

A closure is very similar to a function, except it has no name and can be written inline wherever an expression is expected. In fact, Swift's named functions are simply closures with a name attached. A closure's type looks like (ParameterTypes) -> ReturnType, and you can store one in a constant or variable just like any other value, then call it later using the same parentheses syntax you'd use to call a function.

A basic closure

let greet: (String) -> String = { name in
    return "Hello, \(name)!"
}

print(greet("Priya"))   // Hello, Priya!

Capturing Values from the Surrounding Scope

A closure can 'capture' constants and variables from the context in which it's defined, keeping a reference to them even after that context has finished executing. This is what allows a function that returns a closure to hand back a piece of state along with the behavior that uses it. Each call to the outer function creates a fresh, independent capture, so two closures produced by the same factory function don't share state with each other.

Capturing state in a counter

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

let counter = makeCounter()
print(counter())   // 1
print(counter())   // 2
print(counter())   // 3

Trailing Closure Syntax

When a function's last parameter is a closure, Swift lets you move that closure outside the parentheses entirely — this is trailing closure syntax, and it's used constantly with functions like sorted(by:), map, and filter. Inside the closure, Swift can often infer the parameter and return types from context, letting you drop them, and for very short closures you can skip naming parameters altogether and use the automatically provided shorthand names $0, $1, and so on.

Trailing closures and shorthand arguments

let scores = [42, 7, 88, 15, 63]

let descending = scores.sorted { $0 > $1 }
print(descending)   // [88, 63, 42, 15, 7]

let names = ["Sam", "Zoe", "Ann"]
let sortedNames = names.sorted { first, second in
    first < second
}
print(sortedNames)   // ["Ann", "Sam", "Zoe"]
  • $0, $1, ... refer to closure parameters by position, no names needed
  • Drop return for a single-expression closure body — it's implied
  • Parameter and return types can usually be inferred from context
  • Parentheses around the argument list can be omitted entirely for a trailing closure
FormExampleBest For
Full syntax{ (a: Int, b: Int) -> Bool in a < b }Clarity, or a complex multi-line body
Inferred types{ a, b in a < b }Shorter code when context supplies the types
Shorthand args{ $0 < $1 }Very short, single-purpose closures
Trailing closurescores.sorted { $0 < $1 }The common case: last-argument closures like map, filter, sorted
Note: Here's the gotcha that trips people up: closures capture variables by reference, not by taking a snapshot of the value. If a closure captures a var and something else changes it before the closure runs, the closure sees the new value, not the one that existed when it was created.

Exercise: Swift Closures

If a closure captures a `var` from its surrounding scope, and that variable changes afterward, what does the closure see?