# Swift Closures Explained: Syntax, Capture Values, and Uses

Source: https://devdocs10.com/articles/swift/swift-closures  
By Murat Bo · Published 2026-05-18 · DevDocs10 (https://devdocs10.com)

Swift closures are one of the most important language features to understand because they are used everywhere: sorting arrays, handling completion callbacks, transforming collections, and passing behavior into functions. In this article, you will learn what closures are, how Swift closure syntax works, how value capture behaves, and how to avoid the mistakes that beginners commonly make.

> **Quick answer:** A closure in Swift is a self-contained block of code that you can store in a variable, pass to a function, and run later. You can think of it as a function value, and unlike a named function, a closure can capture values from the surrounding scope.

> **Difficulty:** Intermediate
>
> **Helpful to know first:** basic Swift syntax, how functions are declared and called, and common types like `String`, `Int`, and arrays.

## 1. What Is a Closure?

A closure is a reusable block of code that can be treated like a value. You can assign it to a variable, pass it as an argument, return it from another function, and execute it when needed.

- A closure can take input values and return an output value.
- A closure can be anonymous, which means it does not need a function name.
- A closure can capture values from the scope where it was created.
- Many Swift standard library methods, such as `sorted`, `map`, and `filter`, rely on closures.
- Closures are closely related to functions. In Swift, functions are actually a special kind of closure with a name.

That last point is important for beginners: closures and functions are not unrelated concepts. A named function can often be used anywhere Swift expects a closure with the same parameter and return types.

## 2. Why Closures Matters

Closures matter because they let you pass behavior, not just data. Instead of writing a function that always performs one hard-coded action, you can write a function that accepts a closure and lets the caller decide what should happen.

That makes code more flexible and more reusable. For example, rather than writing separate functions to sort numbers ascending and descending, you can write one sorting function and pass in the comparison logic as a closure.

Closures are especially useful when:

- You want to customize how a function behaves.
- You need to run code later, such as after a task finishes.
- You are transforming or filtering collections.
- You want small pieces of logic close to where they are used.

Closures are less appropriate when the logic is large, reused across many places, or deserves a descriptive name. In those cases, a named function is often clearer.

## 3. Basic Syntax

The general closure expression syntax in Swift looks like this:

```swift
{ (parameters) -> ReturnType in
    // code to run
}
```

The parameter list and return type come before the `in` keyword. The body of the closure comes after `in`.

### A simple closure stored in a variable

Here is a basic closure that takes two integers and returns their sum:

```swift
let add = { (a: Int, b: Int) -> Int in
    return a + b
}

let result = add(4, 6)
print(result)
```

Output (tested with Swift 6.4):

```text
10
```

This closure is assigned to `add`. Because closures are values, you can call the variable like a function. The output is `10`.

### A function that accepts a closure

Closures become more useful when you pass them into functions:

```swift
func performOperation(
    on x: Int,
    using operation: (Int) -> Int
) -> Int {
    return operation(x)
}

let doubled = performOperation(on: 5) { number in
    number * 2
}

print(doubled)
```

Output (tested with Swift 6.4):

```text
10
```

The parameter named `operation` expects a closure that takes one `Int` and returns an `Int`. The trailing closure syntax makes the function call easier to read.

### Closures vs functions

A named function can often replace a closure:

```swift
func double(number: Int) -> Int {
    number * 2
}

let value = performOperation(on: 5, using: double)
```

Use closures when the logic is short and local. Use a named function when the behavior needs a meaningful name or will be reused.

## 4. Step-by-Step Examples

### Example 1: Sorting an array

One of the most common closure examples is sorting. The closure decides whether one element should come before another.

```swift
let scores = [88, 92, 75, 100]

let highestFirst = scores.sorted { first, second in
    first > second
}

print(highestFirst)
```

Output (tested with Swift 6.4):

```text
[100, 92, 88, 75]
```

The closure receives two elements and returns `true` when the first should appear before the second. The result is a descending array.

### Example 2: Transforming values with map

The `map` method uses a closure to transform each element into a new value.

```swift
let prices = [10, 25, 40]

let formattedPrices = prices.map { price in
    "$\(price)"
}

print(formattedPrices)
```

Output (tested with Swift 6.4):

```text
["$10", "$25", "$40"]
```

Each integer becomes a string. The closure defines the transformation for one item, and `map` applies it to the whole array.

### Example 3: Capturing values from the surrounding scope

Closures can remember values from the place where they were created. This is called capturing values.

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

let counterA = makeCounter()
print(counterA())
print(counterA())
print(counterA())
```

Output (tested with Swift 6.4):

```text
1
2
3
```

Even after `makeCounter` finishes, the closure still remembers `count`. This is one of the most powerful differences between closures and simple code blocks.

### Example 4: Using shorthand argument names

Swift lets you shorten closure syntax when types are already known from context.

```swift
let names = ["Taylor", "Alex", "Morgan"]

let alphabetical = names.sorted { $0 < $1 }

print(alphabetical)
```

Output (tested with Swift 6.4):

```text
["Alex", "Morgan", "Taylor"]
```

Here, `$0` means the first argument and `$1` means the second. This style is compact, but it should be used only when the meaning stays obvious.

### Example 5: Escaping closures for later execution

A closure is `escaping` when the function stores it or uses it after the function returns.

```swift
var savedAction: (() -> Void)?

func storeAction(action: @escaping () -> Void) {
    savedAction = action
}

storeAction {
    print("Action ran later")
}

savedAction?()
```

Output (tested with Swift 6.4):

```text
Action ran later
```

The `@escaping` attribute is required because the closure outlives the function call. This is common in completion handlers and asynchronous code.

## 5. Practical Use Cases

- Sorting product lists, dates, or scores with custom comparison rules.
- Transforming API data into display-friendly strings with `map`.
- Filtering arrays for search results with `filter`.
- Passing completion handlers to code that performs work and responds later.
- Building reusable utility functions that accept custom behavior from the caller.
- Creating small stateful utilities, such as counters or accumulators, through captured values.

## 6. Common Mistakes

### Mistake 1: Forgetting the `in` keyword in a full closure expression

When you write an explicit parameter list in a closure, Swift expects the `in` keyword before the body.

**Problem:** This closure declares parameters and a return type, but it does not separate the signature from the body with `in`, so Swift cannot parse it correctly.

```swift
let multiply = { (a: Int, b: Int) -> Int
    a * b
}
```

**Fix:** Add `in` between the signature and the closure body.

```swift
let multiply = { (a: Int, b: Int) -> Int in
    a * b
}
```

The corrected version works because Swift now knows where the closure signature ends and the executable body begins.

### Mistake 2: Using an escaping closure without `@escaping`

If a function stores a closure for later use, the closure escapes the function body and must be marked appropriately.

**Problem:** This code assigns the closure to external storage, so Swift reports an error like `Escaping closure captures non-escaping parameter` unless the parameter is marked `@escaping`.

```swift
var completionHandler: (() -> Void)?

func saveHandler(handler: () -> Void) {
    completionHandler = handler
}
```

**Fix:** Mark the closure parameter with `@escaping` when it will be stored or executed after the function returns.

```swift
var completionHandler: (() -> Void)?

func saveHandler(handler: @escaping () -> Void) {
    completionHandler = handler
}
```

The corrected version works because Swift now knows the closure may live beyond the function call.

### Mistake 3: Capturing `self` strongly in a stored closure

Closures capture referenced values by default. In class-based code, this can create a retain cycle if an object stores a closure that also strongly captures that same object.

**Problem:** The instance keeps the closure alive, and the closure keeps the instance alive. That prevents deallocation and can cause a memory leak.

```swift
class Greeter {
    var name: String
    var sayHello: (() -> Void)?
    
    init(name: String) {
        self.name = name
        self.sayHello = {
            print("Hello, \(self.name)")
        }
    }
}
```

**Fix:** Use a capture list such as `[weak self]` when a stored closure should not keep the object alive.

```swift
class Greeter {
    var name: String
    var sayHello: (() -> Void)?
    
    init(name: String) {
        self.name = name
        self.sayHello = { [weak self] in
            guard let self = self else { return }
            print("Hello, \(self.name)")
        }
    }
}
```

The corrected version works because the closure no longer holds a strong reference that would keep the instance in memory.

### Mistake 4: Overusing shorthand arguments until the closure becomes unclear

Shorthand names like `$0` and `$1` are useful, but they can hurt readability when the logic is more than very simple.

**Problem:** This closure is compact but hard to understand at a glance, especially for beginners or future maintainers.

```swift
let result = ["bob", "alice", "sam"].sorted {
    $0.lowercased() < $1.lowercased()
}
```

**Fix:** Use explicit parameter names when they make the logic easier to read.

```swift
let result = ["bob", "alice", "sam"].sorted { leftName, rightName in
    leftName.lowercased() < rightName.lowercased()
}
```

The corrected version works because the closure still does the same job while making the comparison easier to understand.

## 7. Best Practices

### Practice 1: Prefer the simplest closure syntax that stays readable

Swift supports several levels of shorthand. Use only as much shortening as the reader can still understand easily.

A less readable style can be too compact:

```swift
let sortedNames = names.sorted { $0 < $1 }
```

A clearer version may be better when the code is teaching-oriented or slightly more complex:

```swift
let sortedNames = names.sorted { firstName, secondName in
    firstName < secondName
}
```

This matters because code is read far more often than it is written.

### Practice 2: Use trailing closure syntax when it improves the call site

When a function’s last argument is a closure, trailing closure syntax usually makes the code more natural.

```swift
// Less preferred when the closure is the final argument
let values = [1, 2, 3].map({ number in
    number * 2
})

// Preferred
let doubledValues = [1, 2, 3].map { number in
    number * 2
}
```

This style is common across Swift codebases, especially with collection methods and completion handlers.

### Practice 3: Think carefully about capture semantics

Closures capture values automatically, which is powerful but easy to misuse. Be intentional about whether the closure should keep something alive.

```swift
class Logger {
    var message = "Ready"
    
    func makePrinter() -> () -> Void {
        return { [weak self] in
            print(self?.message ?? "No message")
        }
    }
}
```

This is especially important with classes and stored closures, where strong reference cycles are possible.

## 8. Limitations and Edge Cases

- Closures can make code harder to read if nested too deeply or shortened too aggressively.
- Escaping closures require extra care because they may run later, after surrounding values have changed.
- Stored closures in classes can create retain cycles when they strongly capture `self`.
- Value capture can surprise beginners. A closure may keep using a captured variable even after the original scope has ended.
- Type inference usually helps, but sometimes Swift reports errors such as `Unable to infer complex closure return type` or `Contextual type for closure argument list expects 1 argument` when the closure shape does not match the expected function type.
- Non-escaping closures are safer and more efficient in some cases, but they cannot be stored for later use.

> If a closure seems confusing, try adding explicit parameter types and a return type first. Once the code is working, you can simplify it carefully.

## 9. Practical Mini Project

This mini project builds a simple list processor. It uses closures to filter, transform, and sort strings, which mirrors common real-world Swift code.

```swift
func processNames(
    _ names: [String],
    filter: (String) -> Bool,
    transform: (String) -> String,
    sortBy: (String, String) -> Bool
) -> [String] {
    let filtered = names.filter(filter)
    let transformed = filtered.map(transform)
    return transformed.sorted(by: sortBy)
}

let names = ["Taylor", "alex", "Sam", "jo"]

let result = processNames(
    names,
    filter: { name in
        name.count >= 3
    },
    transform: { name in
        name.uppercased()
    },
    sortBy: { left, right in
        left < right
    }
)

print(result)
```

Output (tested with Swift 6.4):

```text
["ALEX", "SAM", "TAYLOR"]
```

This example shows why closures are so useful: the function itself stays generic, while the caller defines the behavior. You can change filtering, formatting, or sorting without rewriting the function.

## 10. Key Points

- A closure is a self-contained block of code that can be passed around like a value.
- Closures can take parameters, return values, and capture surrounding variables.
- Functions are a named form of closure in Swift.
- Trailing closure syntax improves readability when a closure is the last argument.
- Use `@escaping` when a closure is stored or executed after the function returns.
- Use capture lists such as `[weak self]` to avoid retain cycles in class-based code.
- Short closure syntax is convenient, but readability should come first.

## 11. Practice Exercise

Create a function named `applyTwice` that accepts an `Int` and a closure of type `(Int) -> Int`. The function should apply the closure to the number two times and return the final result.

- Call the function with the value `3`.
- Use a closure that adds `2` to the input.
- Print the result.

**Expected output:** `7`

**Hint:** Run the closure once, store the intermediate result, then run it again on that result.

```swift
func applyTwice(to value: Int, using operation: (Int) -> Int) -> Int {
    let firstResult = operation(value)
    let secondResult = operation(firstResult)
    return secondResult
}

let result = applyTwice(to: 3) { number in
    number + 2
}

print(result)
```

Output (tested with Swift 6.4):

```text
7
```

## 12. Final Summary

Swift closures let you write behavior as a value. That means you can store code, pass it into functions, return it from functions, and execute it later. Once you understand closure syntax, trailing closures, shorthand arguments, and value capture, a huge amount of Swift code becomes much easier to read.

The most important ideas to keep in mind are that closures can capture surrounding values, escaping closures live beyond the function call, and stored closures in classes may need capture lists like `[weak self]`. If you want to get more comfortable with closures, a strong next step is to practice them with collection methods such as `map`, `filter`, `reduce`, and `sorted`.
