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 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.
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:
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.
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:
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 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:
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.
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:
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.
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
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 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.
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.
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.
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
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.
Read also