SWIFT

Swift Defer Statement: Syntax, Usage, and Practical Examples

The defer statement in Swift lets you schedule code to run just before the current scope exits—no matter how that exit occurs. This makes it ideal for managing cleanup tasks like closing files, releasing resources, or resetting state.

By centralizing finalization logic, defer enhances readability and ensures consistent execution, even when early returns or errors occur. It’s a simple but powerful tool for writing safer, more maintainable code.


What Does defer Do?

A defer block delays its execution until the surrounding scope finishes. This could be a function, loop, or conditional. Regardless of how that scope exits—via return, break, continue, or throw—the deferred code will run.

Example:

func example() {
    defer {
        print("This runs at the end")
    }
    print("This runs first")
}

Output:

This runs first
This runs at the end

The deferred block runs after the rest of the function body but before the function returns.


Syntax Overview

The structure is straightforward:

defer {
    // Code to run before exiting scope
}

You can include multiple defer blocks. They’ll execute in reverse order—last-in, first-out (LIFO).


Common Use: Resource Cleanup

A typical use case is cleanup—ensuring that resources are released, even if the function exits early:

func readFile() {
    let file = openFile("data.txt")
    defer {
        closeFile(file)
    }

    print("Reading from file")
    // Early return or error won’t skip closing the file
}

The cleanup logic is kept close to where the resource is used, improving safety and clarity.


Multiple Defer Blocks

Each block is pushed onto a stack and executed in reverse order:

func demo() {
    defer {
        print("Last defer")
    }
    defer {
        print("First defer")
    }
    print("Function body")
}

Output:

Function body
First defer
Last defer

This behavior makes it easy to layer cleanup logic without interfering with control flow.


Error Handling with Defer

Deferred blocks are especially helpful in throwing functions. They ensure critical actions—like logging, resetting state, or closing resources—always happen before the error propagates.

func loadData() throws {
    print("Opening database")
    defer {
        print("Closing database")
    }

    throw NSError(domain: "", code: 1, userInfo: nil)
}

Output:

Opening database
Closing database

Even though an error is thrown, the deferred code still executes.


Working with Guard Statements

Combining guard with defer allows early exits while keeping finalization logic centralized:

func process(input: String?) {
    defer {
        print("Finished processing")
    }

    guard let value = input else {
        print("Input is nil")
        return
    }

    print("Processing \(value)")
}

Output for nil input:

Input is nil
Finished processing

Output for valid input:

Processing Hello
Finished processing

No matter the path taken, the cleanup code runs.


Practical Use Cases

Typical scenarios where defer shines:

  • Releasing file handles
  • Closing database connections or network sockets
  • Unlocking mutexes or semaphores
  • Resetting temporary states
  • Logging function exits for debugging
  • Rolling back partial work in transactional code

The key advantage is having cleanup logic in one predictable place.


Comparison to finally in Other Languages

In other languages, cleanup logic is typically handled using finally blocks:

  • Java: try { ... } finally { ... }
  • Python: try: ... finally: ...

Swift’s defer plays a similar role but doesn’t require try. It can be placed anywhere in a scope and is always executed when that scope ends.


Scoped Behavior in Loops

Each scope maintains its own defer stack. This means defer blocks inside loops or conditional branches only run when that specific scope exits.

for i in 1...3 {
    defer {
        print("Deferred for \(i)")
    }
    print("Loop iteration \(i)")
}

Output:

pgsql
CopyEdit
Loop iteration 1
Deferred for 1
Loop iteration 2
Deferred for 2
Loop iteration 3
Deferred for 3

Each iteration handles its own deferred logic independently.


Performance Considerations

Using defer introduces a small overhead because Swift must track the deferred blocks. For most use cases, this is negligible. However, in tight loops or performance-critical sections, consider whether the clarity is worth the cost.

In cleanup-heavy code or error-prone logic, the safety and maintainability usually outweigh performance concerns.


Common Mistakes

To get the most out of defer, avoid these pitfalls:

  • Skipping an exit path: The deferred block won’t run if the scope never exits.
  • Overusing multiple blocks: Too many defers in one scope can become hard to follow—use comments or group them logically.
  • Putting heavy logic inside defer: Keep deferred blocks concise and focused on cleanup or finalization.
  • Misunderstanding execution order: Remember—defer blocks are executed in reverse.

Clear naming and structure can help avoid confusion when multiple blocks are involved.


Summary

The defer statement is a reliable and elegant way to guarantee that cleanup code always runs before a scope ends. It reduces duplication, improves clarity, and prevents subtle bugs—especially in code with multiple exit paths or error handling.

By placing deferred actions where resources are acquired, you ensure consistent cleanup without cluttering your logic. Whether you're managing files, network sessions, or UI state, defer helps you write safer, cleaner Swift code.

Learn to Code in Swift for Free
Start learning now
button icon
To advance beyond this tutorial and learn Swift by doing, try the interactive experience of Mimo. Whether you're starting from scratch or brushing up your coding skills, Mimo helps you take your coding journey above and beyond.

Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.

You can code, too.

© 2025 Mimo GmbH

Reach your coding goals faster