# Swift defer: How to Run Cleanup Code Before Scope Exit

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

Swift’s `defer` statement lets you schedule code to run right before the current scope ends. It is especially useful for cleanup tasks such as unlocking a lock, closing a resource, resetting temporary state, or ensuring important finishing work happens even when a function returns early.

> **Quick answer:** In Swift, `defer` delays a block of code until execution leaves the current scope. Deferred blocks always run before the scope exits, and if you declare multiple `defer` blocks, they run in reverse order.

> **Difficulty:** Beginner
>
> **Helpful to know first:** You’ll understand this better if you know basic Swift syntax, how functions work, and what it means for code to enter and leave a scope.

## 1. What Is defer?

`defer` is a control flow feature in Swift that registers a block of code to run later, specifically when the current scope is about to exit.

- It runs when the current scope ends, not when the line is reached.
- It works inside functions, loops, conditionals, and other scoped blocks.
- It is commonly used for cleanup and guaranteed finishing steps.
- It still runs if the scope exits through an early `return`.
- Multiple deferred blocks run in last-in, first-out order.

You can think of `defer` as Swift’s built-in way to say, “Before leaving this scope, make sure this code runs.”

> A common comparison is between `defer` and a `finally` block in other languages. They are similar in purpose, but `defer` is tied to scope exit rather than being written as part of a try-catch-finally structure.

## 2. Why defer Matters

Many bugs happen because cleanup code is forgotten. A function may return early, hit a condition branch, or become more complex over time. When cleanup is repeated in several places, it becomes easy to miss one path.

`defer` matters because it keeps setup code and cleanup intent close together. That makes the code safer and easier to read.

- It reduces duplicated cleanup code.
- It makes early returns safer.
- It helps maintain consistent state changes.
- It improves readability for resource management patterns.

Use `defer` when something must happen before leaving a scope. Do not use it for ordinary business logic that should run immediately in normal reading order.

## 3. Basic Syntax

### How the syntax looks

The smallest useful example is a function that prints one message immediately and one message when the function is about to finish.

```swift
func example() {
    defer {
        print("This runs before the function exits")
    }

    print("This runs first")
}

example()
```

Output (tested with Swift 6.4):

```text
This runs first
This runs before the function exits
```

When this function runs, the second `print` statement appears first because the `defer` block is saved for later. Then, right before the function exits, the deferred code runs.

### What happens step by step

Here is the same idea in plain terms:

- Swift reaches the `defer` statement.
- It records the block to run later.
- The rest of the scope continues normally.
- When the scope is about to end, Swift executes the deferred block.

### defer with early return

This example shows why `defer` is useful. Even if the function returns early, the cleanup still happens.

```swift
func process(isValid: Bool) {
    defer {
        print("Finished processing")
    }

    if !isValid {
        print("Invalid input")
        return
    }

    print("Processing successful")
}
```

Whether the input is valid or not, `Finished processing` will be printed before the function exits.

## 4. Step-by-Step Examples

### Example 1: Cleanup after temporary state changes

Sometimes you temporarily change a value and want to guarantee it is restored before leaving the function.

```swift
var isLoading = false

func loadData() {
    isLoading = true

    defer {
        isLoading = false
    }

    print("Loading data...")
}
```

This keeps the reset logic near the setup. If the function later gains more returns or conditions, the loading state still gets restored.

### Example 2: Unlocking a lock safely

A classic use of `defer` is making sure a lock is always released after it is acquired.

```swift
final class SimpleLock {
    func lock() {
        print("Locked")
    }

    func unlock() {
        print("Unlocked")
    }
}

let lock = SimpleLock()

func updateSharedState() {
    lock.lock()
    defer {
        lock.unlock()
    }

    print("Updating shared state")
}
```

This pattern prevents a common bug where one return path unlocks the lock but another path forgets to do it.

### Example 3: Multiple defer blocks and execution order

If a scope contains more than one `defer` block, Swift runs them in reverse order. This is important to understand.

```swift
func showOrder() {
    defer {
        print("First defer")
    }

    defer {
        print("Second defer")
    }

    print("Function body")
}
```

The output order is:

```swift
Function body
Second defer
First defer
```

This last-in, first-out behavior is useful when cleanup steps need to happen in the opposite order of setup.

### Example 4: defer inside a loop scope

`defer` runs when the current scope ends. In a loop, that means the end of each iteration if the deferred block is declared inside the loop body.

```swift
for number in 1...3 {
    defer {
        print("Finished iteration \(number)")
    }

    print("Working on \(number)")
}
```

Output (tested with Swift 6.4):

```text
Working on 1
Finished iteration 1
Working on 2
Finished iteration 2
Working on 3
Finished iteration 3
```

Each deferred block runs at the end of its own loop iteration, not at the end of the whole loop.

## 5. Practical Use Cases

- Resetting a temporary flag such as `isLoading` or `isSaving` before a function exits.
- Unlocking a mutex or custom lock after critical section code finishes.
- Ending a logging, tracing, or timing section after a function returns.
- Closing or releasing resources that should be cleaned up regardless of which return path is taken.
- Restoring a previous configuration value that was only changed temporarily inside a scope.
- Guaranteeing final status messages or metrics reporting before leaving a function.

## 6. Common Mistakes

### Mistake 1: Expecting defer to run immediately

Beginners sometimes read a `defer` block as if it executes at that exact line. It does not. Swift schedules it for the end of the current scope.

**Problem:** This code assumes the deferred assignment happens before the next line, so the printed value is not what the programmer expects.

```swift
func wrongExpectation() {
    var message = "Start"

    defer {
        message = "Finished"
    }

    print(message)
}
```

**Fix:** Remember that the deferred block runs only when the scope exits. If you need the value changed now, assign it directly before using it.

```swift
func correctExpectation() {
    var message = "Start"
    message = "Finished"
    print(message)
}
```

The corrected version works because the assignment happens in normal execution order rather than being delayed until scope exit.

### Mistake 2: Forgetting that multiple defer blocks run in reverse order

When there are several deferred blocks, Swift executes the most recently declared one first. Assuming top-to-bottom order can produce surprising behavior.

**Problem:** This code expects cleanup to happen in declaration order, but Swift uses last-in, first-out order for deferred blocks.

```swift
func wrongOrder() {
    defer {
        print("Close file")
    }

    defer {
        print("Flush buffer")
    }
}
```

**Fix:** Declare deferred blocks with reverse execution in mind. The last one written will run first.

```swift
func correctOrder() {
    defer {
        print("Flush buffer")
    }

    defer {
        print("Close file")
    }
}
```

The corrected version works because the declarations are arranged to match Swift’s reverse execution order.

### Mistake 3: Using defer for code that should be explicit

`defer` is great for cleanup, but it can make ordinary program flow harder to read if overused.

**Problem:** This code hides normal business logic in a deferred block, which makes the function’s behavior less obvious and harder to maintain.

```swift
func saveName() {
    var name = "Taylor"

    defer {
        print("Saved \(name)")
    }

    name = "Morgan"
}
```

**Fix:** Use `defer` for cleanup or guaranteed final actions. For normal logic, write the code where it actually happens.

```swift
func saveName() {
    let name = "Morgan"
    print("Saved \(name)")
}
```

The corrected version works because the important action is expressed directly in the main flow of the function.

### Mistake 4: Confusing defer with error handling

`defer` is not a replacement for `do`, `try`, and `catch`. It can help with cleanup during error handling, but it does not catch errors by itself.

**Problem:** This code may throw an error, but there is no error handling. The deferred block still runs, yet the thrown error must still be handled properly.

```swift
enum FileError: Error {
    case missing
}

func readFile() throws {
    defer {
        print("Cleanup complete")
    }

    throw FileError.missing
}
```

**Fix:** Use `defer` for cleanup and use Swift’s error handling syntax to catch or propagate errors correctly.

```swift
enum FileError: Error {
    case missing
}

func readFile() throws {
    defer {
        print("Cleanup complete")
    }

    throw FileError.missing
}

do {
    try readFile()
} catch {
    print("Handled error: \(error)")
}
```

Output (tested with Swift 6.4):

```text
Cleanup complete
Handled error: missing
```

The corrected version works because cleanup is guaranteed by `defer` while the error is handled through Swift’s normal throwing and catching rules.

## 7. Best Practices

### Use defer for cleanup that must always happen

The best use of `defer` is guaranteed cleanup. This keeps setup and cleanup easy to pair mentally.

```swift
func performTask() {
    print("Task started")

    defer {
        print("Task finished")
    }

    print("Doing work")
}
```

This is clearer than repeating the finishing step before every possible return.

### Place defer soon after the setup it cleans up

A good habit is to write the `defer` block right after the setup action. That way the connection is obvious.

```swift
let lock = SimpleLock()

func safeUpdate() {
    lock.lock()
    defer {
        lock.unlock()
    }

    print("Critical work")
}
```

This pattern makes the lifecycle of the lock easy to understand at a glance.

### Keep deferred blocks small and focused

A deferred block should usually do one clear cleanup task. Large deferred blocks can hide too much behavior.

```swift
func processItem() {
    var didStart = true

    defer {
        didStart = false
    }

    print("Processing item")
}
```

A short deferred block is easier to trust and reason about than one that contains lots of unrelated logic.

## 8. Limitations and Edge Cases

- `defer` runs when the current scope exits, so its timing depends on the exact scope where it is declared.
- If you put `defer` inside a loop body, it runs at the end of each iteration, not after the whole loop finishes.
- Multiple deferred blocks run in reverse order, which can surprise developers who expect top-to-bottom execution.
- `defer` does not replace error handling. It guarantees cleanup, but thrown errors must still be handled or propagated.
- Using `defer` for normal logic can reduce readability because the important action is delayed and visually separated from where readers expect it.
- A common “not working” complaint is really a scope misunderstanding: the deferred code does run, but only when that specific block exits.

## 9. Practical Mini Project

This small example simulates a file import task. It sets a loading flag, starts the import, returns early on invalid input, and still guarantees that the loading flag is reset before the function exits.

```swift
var isImporting = false

func importRecords(from path: String) {
    isImporting = true
    print("Import started. isImporting = \(isImporting)")

    defer {
        isImporting = false
        print("Import ended. isImporting = \(isImporting)")
    }

    if path.isEmpty {
        print("No file path provided")
        return
    }

    print("Reading records from \(path)")
    print("Import successful")
}

importRecords(from: "customers.csv")
importRecords(from: "")
```

Output (tested with Swift 6.4):

```text
Import started. isImporting = true
Reading records from customers.csv
Import successful
Import ended. isImporting = false
Import started. isImporting = true
No file path provided
Import ended. isImporting = false
```

This mini project shows the real value of `defer`. The cleanup code is written once, but it still works for both the successful path and the early return path.

## 10. Key Points

- `defer` schedules code to run when the current scope exits.
- It is most useful for cleanup and guaranteed finishing tasks.
- Deferred code runs even when a function returns early.
- Multiple deferred blocks run in reverse order.
- `defer` is not a substitute for normal control flow or error handling.
- Placing cleanup logic near setup code improves readability and safety.

## 11. Practice Exercise

Try this exercise to make the behavior of `defer` feel natural.

- Create a function named `downloadFile` that sets a Boolean named `isDownloading` to `true`.
- Use `defer` to make sure `isDownloading` becomes `false` before the function exits.
- If the file name is empty, print an error message and return early.
- Otherwise, print that the download is happening.

**Expected output:** The flag should always end as `false`, whether the function returns early or completes normally.

**Hint:** Set the flag first, add the `defer` block immediately after, and then write the conditional return logic.

```swift
var isDownloading = false

func downloadFile(named fileName: String) {
    isDownloading = true
    print("Started. isDownloading = \(isDownloading)")

    defer {
        isDownloading = false
        print("Finished. isDownloading = \(isDownloading)")
    }

    if fileName.isEmpty {
        print("Missing file name")
        return
    }

    print("Downloading \(fileName)...")
}

downloadFile(named: "report.pdf")
downloadFile(named: "")
```

Output (tested with Swift 6.4):

```text
Started. isDownloading = true
Downloading report.pdf...
Finished. isDownloading = false
Started. isDownloading = true
Missing file name
Finished. isDownloading = false
```

## 12. Final Summary

Swift’s `defer` statement is a small feature with a big practical benefit. It lets you write code that must run before a scope exits, which makes cleanup safer and helps prevent bugs caused by early returns or forgotten finishing steps.

In this article, you learned what `defer` does, how its syntax works, why execution order matters, and where it fits best in real code. You also saw common mistakes, best practices, and a mini project that showed how `defer` can simplify state cleanup.

A good next step is to practice `defer` together with Swift functions, error handling, and scope rules so you can recognize the situations where it makes your code clearer and safer.
