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

Watch out
  • Forgetting @escaping on a closure parameter that's stored or called later, instead of being called before the function returns.
  • Creating a strong reference cycle by capturing self strongly 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

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

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.