Swift
Swift Closures
Closures are one of the first Swift features that feel unfamiliar coming from other languages — and one of the most common topics in iOS interviews. This guide covers what they are, how to write them, and where you'll actually use them.
What is a Closure?
A closure is a self-contained block of functionality that can be passed around and used in your code, much like a function. Closures can capture and store references to variables and constants from the surrounding context in which they're defined — this is called capturing those values.
In fact, Swift functions are just a special case of closures. Anywhere you can pass a function, you can pass a closure instead.
Basic Syntax
A closure expression has the following general form:
{ (parameters) -> ReturnType in
statements
}
For example, a closure that adds two integers:
let add: (Int, Int) -> Int = { (a, b) -> Int in
return a + b
}
add(2, 3) // 5
Parameters and Return Values
Closures can take parameters and return values just like functions. Swift can often infer parameter and return types from context, which lets you drop them entirely:
let numbers = [5, 2, 8, 1]
let sorted = numbers.sorted(by: { (a: Int, b: Int) -> Bool in
return a < b
})
// Types inferred from `sorted(by:)`, so this is equivalent:
let sortedShort = numbers.sorted(by: { a, b in a < b })
A single-expression closure can also drop the
return keyword, since Swift implicitly
returns the result of that one expression.
Trailing Closures
When a closure is the last argument to a function, Swift lets you write it after the parentheses instead of inside them. This is called a trailing closure, and it's the style you'll see most often in real Swift and SwiftUI code.
// Without a trailing closure
let sortedNumbers = numbers.sorted(by: { a, b in a < b })
// With a trailing closure
let sortedNumbersTrailing = numbers.sorted { a, b in a < b }
// Shorthand argument names ($0, $1) go a step further
let sortedNumbersShorthand = numbers.sorted { $0 < $1 }
Capturing Values
A closure can capture variables and constants from its surrounding scope and use — or even modify — them later, even after that scope has finished executing.
func makeCounter() -> () -> Int {
var count = 0
return {
count += 1
return count
}
}
let counter = makeCounter()
counter() // 1
counter() // 2
counter() // 3
Each call to makeCounter() creates a new,
independent count variable that its returned
closure captures by reference.
Common iOS Example
Closures show up constantly in iOS development, most often as completion handlers for asynchronous work:
func fetchTempleList(completion: @escaping (Result<[Temple], Error>) -> Void) {
URLSession.shared.dataTask(with: templesURL) { data, _, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data,
let temples = try? JSONDecoder().decode([Temple].self, from: data) else {
completion(.failure(URLError(.cannotParseResponse)))
return
}
completion(.success(temples))
}.resume()
}
The @escaping keyword marks that the closure
may be called after the function returns — necessary
here because the network request finishes asynchronously.
Common Mistakes
-
Forgetting
@escapingon a closure parameter that's stored or called later, instead of being called before the function returns. -
Creating a strong reference cycle by capturing
selfstrongly inside a long-lived closure (for example, one stored on a view controller or view model). Use[weak self]when the closure might outlive the object that created it. - Assuming a closure runs synchronously just because it was written inline — asynchronous APIs run their closures later, not immediately.
// Avoid a retain cycle when self may outlive the closure
networkService.fetchTempleList { [weak self] result in
guard let self else { return }
self.handle(result)
}
Interview Tip
If you're asked "what's the difference between a
function and a closure?", the honest answer is: very
little — a named function is really just a closure with
a name and no captured values. A strong follow-up is
being able to explain why [weak self]
matters, since it's one of the most commonly asked
closure-related questions in iOS interviews.
Key Takeaways
- A closure is a block of code that can capture and store references to values from its surrounding context.
- Swift functions are a special case of closures.
- Trailing closure syntax is the idiomatic way to pass a closure as the last argument.
- Closures capture variables by reference, not by value — this is what makes them powerful and occasionally surprising.
- Use
[weak self]in closures that may outlive the object that created them, to avoid retain cycles.