defer — what it is, syntax and execution order

Author: IT Sectr Published: 2026-06-20 Reading time: 8 min

defer is a control flow construct in Swift that schedules a block of code to execute upon exiting the current scope. The defer block executes regardless of how the scope ends — return, break, throw, fatalError or normal completion. According to the Swift Language Guide (2025), when multiple defers are present in one scope, they execute in reverse order of declaration — the last declared runs first (LIFO). This makes defer indispensable for guaranteed resource cleanup: closing file descriptors, releasing locks, freeing temporary pointers without the risk of missing cleanup upon early exit.

Key Takeaways

  • defer — a block of code executed upon exiting a scope, regardless of the exit reason (return, throw, break)
  • LIFO order: multiple defers execute bottom-up — the last declared runs first
  • Resource cleanup — main use case: closing files, releasing locks, completing animations
  • Variable evaluation: defer sees variables at the moment of exit, not at the moment of declaration
  • Does not replace do-catch for error handling — defer handles cleanup, not flow control

What is defer?

defer is a control flow construct in Swift, introduced in Swift 2.0 (2015), that postpones the execution of its block until the current scope terminates. The key feature: defer guarantees that its body executes regardless of how the scope ends — successfully (return), with an error (throw), prematurely (break, continue), or fatally (fatalError, precondition).

Syntactically, defer looks like defer { /* code */ } and can be placed anywhere inside a scope. The Swift compiler guarantees that the code inside defer will execute even if an exception or return occurs between the defer declaration and the end of the scope. This fundamentally distinguishes defer from regular code placed at the end of a function, which may be skipped upon early exit.

According to an article by Chris Lattner (Swift Creator, 2015), defer was inspired by similar constructs in other languages — defer in Go, finally in Java/Python, scope guard in C++ — but with an important difference: in Swift, defer executes at the end of the scope, not immediately after a try-catch block. This provides more predictable behavior for cleanup in functions with multiple exit points.

Use defer for symmetric resource management: open file → defer { close }, acquire lock → defer { unlock }. This pattern guarantees that resource release is never missed under any circumstances.

Execution order of multiple defers

When multiple defers are declared in the same scope, they execute in reverse order of declaration (LIFO — Last In, First Out). This means the last declared defer runs first, and the first runs last:

swift
func exampleDeferOrder() {
    defer { print("First defer") }
    defer { print("Second defer") }
    defer { print("Third defer") }

    print("Function body")
}
// Output:
// Function body
// Third defer
// Second defer
// First defer

The LIFO order is important for correct management of nested resources. If file A is opened first, then file B, they must be released in reverse order: first B, then A. With defer, this happens automatically — declare a defer right after opening each resource, and the cleanup order will be correct regardless of the number of exit points in the function.

According to Swift by Sundell (2024), this feature makes defer ideal for nested locks and transactions: acquire lock → defer { unlock } → acquire next → defer { unlock }. LIFO guarantees locks are released in reverse acquisition order, preventing deadlocks.

Defer for resource cleanup

The main use case for defer is guaranteed resource cleanup. Consider working with the file system. Opening a file via FileHandle requires explicit closing — defer guarantees that close will be called in any scenario:

swift
func readFile(path: String) throws -> String {
    let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))
    defer { try? handle.close() }

    let data = try handle.readToEnd()
    guard let data else { throw FileError.empty() }

    return String(data: data, encoding: .utf8) ?? ""
    // handle.close() will be called even on throw or return
}

Another typical scenario is UI animations with a loading flag. Before starting a load, set the flag isLoading = true, and defer sets it back to false upon function exit, regardless of request success or failure. This prevents the flag from remaining true due to an unhandled error, which would block the interface forever.

According to the Bitbucket Engineering Blog (2024), defer is also used for profiling: record the time at the beginning of a function, and in defer — calculate and output the difference. This gives accurate performance measurements for all execution paths, including erroneous ones.

Defer and error handling

defer works effectively with throws functions. When a function may throw an error at any stage, defer guarantees cleanup without duplicating code in every catch block or guard early exit:

swift
func processTransaction() throws {
    let db = try openDatabase()
    defer { closeDatabase(db) }

    let user = try fetchUser(from: db)
    defer { logAudit(user) }

    let result = try performPayment(user)
    sendNotification(result)
    // closeDatabase(db) and logAudit(user) will be called
    // on any throw or return
}

Important: defer executes before control is transferred out of the catch block, but after the error occurs. If an error is thrown inside defer, Swift does not allow using try directly inside defer — you need try? or try!. According to Apple Documentation, Swift does not allow an error to escape from defer, as that would violate the guarantee that the block will execute.

Place defer immediately after resource acquisition. This follows the principle of proximity: the reader sees acquisition and release side by side, which improves code reliability and simplifies code review.

Scope rules

defer executes upon exiting the scope in which it is declared. If defer is declared inside a do block, it executes upon exiting that block, not the outer function. If inside a for loop — at each iteration:

swift
func scopeExample() {
    print("start")
    do {
        defer { print("do-block defer") }
        print("inside do")
    }
    // "do-block defer" prints here
    print("after do")
}
    // Output: start, inside do, do-block defer, after do

for i in 1...3 {
    defer { print("end iteration \(i)") }
    print("iteration \(i)")
}
    // Output: iteration 1, end iteration 1, iteration 2, end iteration 2, ...

Variables captured by defer are read at the moment of scope exit, not at the moment of defer declaration. If a variable changes between the defer declaration and the end of the scope, defer will see the latest value. This is an important distinction from closures, where capture happens at creation time. Be careful: changes to a variable after declaring defer will affect its execution.

Common defer mistakes

The first mistake — assuming an execution order other than LIFO. If cleanup order matters and defers are declared in the wrong order, resources may be released with dependency violations. Solution: declare defer immediately after capturing each resource. Second resource opened → defer { close second } before the first is closed.

The second mistake — using defer for logic unrelated to cleanup. defer is meant for guaranteed cleanup, not for main flow control. If code inside defer affects the return value, it's almost always a mistake. defer cannot change the return value of a function (unlike Java finally, where return in finally overwrites the original return).

The third mistake — throwing an error from defer. Swift prohibits try inside defer if the error could propagate outward. Use try? or try! for operations that might throw, or wrap them in a separate function without throws. According to O’Reilly “Swift in Depth” (2025), a good practice is to make cleanup functions non-throwing or handle errors inside defer.

Frequently Asked Questions

What is defer in Swift?

defer is a Swift construct that postpones a block’s execution until the current scope exits. The block always executes — on return, throw, break, or normal completion. It is used for guaranteed resource cleanup: closing files, releasing locks.

In what order do multiple defers execute?

In reverse declaration order (LIFO) — the last declared defer runs first. This guarantees correct cleanup of nested resources: if resource B is opened after A, it will be closed before A, preventing dependencies on already freed resources.

Can you throw an error from defer?

Not directly — Swift prevents error propagation from defer. Use try? or try! for operations that might throw. Best practice is to make cleanup functions non-throwing or handle errors inside defer without propagating outward.

What is the difference between defer and do-catch-finally?

defer is tied to a scope and executes on any exit, including return, throw, and break. finally (in other languages) is tied to try-catch and only executes when try is present. Swift does not have finally — defer fully covers this scenario and works for any scope, not just error handling.

Does defer see variable changes after its declaration?

Yes, defer reads variables at the moment of scope exit, not at the moment of declaration. If a variable changes after defer is declared, the defer block will see the latest value. This differs from regular closures, where capture is fixed at creation time.

Summary

  • defer — a finalization block executed upon scope exit regardless of the reason (return, throw, break, normal completion)
  • LIFO order — multiple defers execute bottom-up, the last declared runs first
  • Resource cleanup — main use case: closing files, releasing locks, freeing pointers, stopping animations
  • Compatibility with throws: defer executes after an error but before exiting the catch block; errors from defer do not propagate
  • Scope: defer executes upon exiting the scope where it was declared — do block, loop, function
  • Variable capture: defer reads values at exit time, not declaration time — be careful with mutations
  • Best practice: declare defer immediately after resource acquisition, do not use defer for business logic, make cleanup functions non-throwing

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also