Swift Defer Tutorial

You write the cleanup code right after the messy part. Swift makes sure it actually runs, no matter what happens.
Written by

Chris C

Updated on

Apr 24 2026

Table of contents

    The Swift defer keyword lets you schedule a block of code to run right before the current scope exits, no matter how it exits. Whether your function returns normally, returns early because of a guard statement, or throws an error, the code inside your defer block will always run.

    Think of it like setting up a “cleanup crew” the moment you open something. You hire them right away, but they do their work on the way out. That way, you never have to remember to call them yourself.

    In this article, you’ll learn exactly how defer works in Swift, walk through the most common ways to use it, see how it shows up in real iOS code, and understand the mistakes that trip up beginners. By the end, you’ll know when to reach for it and when to leave it alone.

    The Basic Syntax

    Here is the simplest possible use of defer in Swift. Read through it, then look at what gets printed and in what order:

    func makeBreakfast() {
        defer {
            print("Washed the dishes")
        }
    
        print("Made coffee")
        print("Made toast")
    }
    
    makeBreakfast()
    
    // Output:
    // Made coffee
    // Made toast
    // Washed the dishes

    Even though “Washed the dishes” is written first in the function, it prints last. That’s defer in action. Let’s break down each part:

    LineWhat it does
    defer { }Registers the code inside the curly braces to run later, right before this function exits. The code isn’t run now — it’s scheduled.
    print("Washed the dishes")This is the deferred work. It will run last, after everything else in the function has finished.
    print("Made coffee")This runs immediately when encountered, as normal. No deferring here.
    print("Made toast")Also runs immediately. At this point the function is about to end, so Swift now runs the deferred block.

    Breaking It Down

    What “scope” means here

    A scope is just the area between a pair of curly braces. A function body is a scope. An if statement is a scope. A for loop is a scope. When Swift sees a defer block, it waits until that surrounding scope is finished, then runs the deferred code.

    For most beginners, the scope you care about is the function. You write defer near the top of a function, and it runs right as the function is about to return.

    defer runs on every exit, not just the normal one

    This is the key insight. Your function might have three different guard statements that can return early. Without defer, you’d need to put your cleanup code in all three places. With defer, you write it once and it runs no matter which exit path is taken.

    Does Swift defer execute on return?

    Yes. The deferred code runs after the return value is prepared but before the function actually hands control back to the caller. This means a defer block cannot change the value a function returns, but it will always run before the return completes.

    Multiple defers run in reverse order

    If you write multiple defer blocks in the same scope, Swift runs them last-in, first-out. Think of it like a stack of plates: the last one you put on top is the first one you take off.

    func stackExample() {
        defer { print("First defer") }   // runs third
        defer { print("Second defer") }  // runs second
        defer { print("Third defer") }   // runs first
        print("Regular code")             // runs first of all
    }
    
    // Output: Regular code, Third defer, Second defer, First defer
    Common gotcha: The reverse order of multiple defers surprises almost every beginner the first time. Just remember: the last defer you wrote is the first one that runs. In practice, most functions only have one defer block, so this rarely causes confusion in real code.

    What is the difference between defer and deinit in Swift?

    defer runs at the end of a scope (usually a function call). deinit runs when a class instance is destroyed and removed from memory. They are completely separate tools. Use defer for function-level cleanup; use deinit for object-level cleanup.

    Variations and Syntax Patterns

    Here are the most important ways you’ll see and use defer in Swift code. Each one builds on the same idea but applies it in a different context.

    defer with guard Handle multiple early exits without repeating cleanup code
    func processUser(id: String?) {
        defer {
            print("Processing finished") // runs no matter what
        }
    
        guard let userId = id else {
            print("No user ID provided")
            return // defer still runs here
        }
    
        guard !userId.isEmpty else {
            print("User ID is empty")
            return // and here
        }
    
        print("Processing user: \(userId)")
    } // and at normal exit too
    This is the most common real-world pattern. You put the cleanup in a defer at the top of the function, then use as many guard statements as you need. No matter which guard triggers, the deferred code always runs.
    defer with throws Guarantee cleanup even when your function can throw errors
    func saveData() throws {
        print("Opening connection")
        defer {
            print("Closing connection") // runs even if an error is thrown
        }
    
        let result = try fetchDataFromServer()
        print("Saving: \(result)")
    }
    When your function uses throws, a thrown error exits the scope immediately. Without defer, your cleanup code below the try line would never run. With defer, it runs even when an error is thrown — equivalent to try/finally in languages like Java or Python.
    defer inside a loop Can you use defer in a loop? Yes — it runs at each iteration’s end
    for item in ["apple", "banana", "cherry"] {
        defer {
            print("Done with \(item)") // runs at end of each iteration
        }
        print("Processing \(item)")
    }
    
    // Processing apple / Done with apple
    // Processing banana / Done with banana
    // Processing cherry / Done with cherry
    The loop body is its own scope, so defer runs at the end of each iteration rather than at the end of the whole loop. This is useful when processing a list of items that each need cleanup, like closing a connection for each request.
    defer inside a do block Narrow the cleanup scope without using a full function
    func processItems() {
        do {
            defer {
                print("Cleaned up the temporary buffer")
            }
            print("Working with temporary data...")
        } // defer runs here, at the end of the do block
    
        print("Back in the main function")
    }
    
    // Working with temporary data...
    // Cleaned up the temporary buffer
    // Back in the main function
    You can use a do { } block to create a temporary scope inside a function. Any defer inside it only runs when that do block ends, not when the function ends. This is handy when you want very precise control over when cleanup happens.
    defer captures variables by reference The deferred code sees the variable’s final value, not its value at the time defer was written
    func captureDemo() {
        var status = "starting"
        defer {
            print("Final status: \(status)") // sees the updated value
        }
        status = "completed"
        print("Did the work")
    }
    
    // Did the work
    // Final status: completed
    When defer runs, it sees the variable’s value at that moment, not the value the variable had when you wrote the defer block. This is useful for logging the final state of something before your function exits.
    defer for UI state reset Reset loading indicators or UI state no matter how an operation ends
    func loadUserProfile() {
        isLoading = true
        defer {
            isLoading = false // always hides the spinner
        }
    
        guard let userId = getCurrentUserId() else { return }
        guard let profile = fetchProfile(for: userId) else { return }
        displayProfile(profile)
    }
    This SwiftUI-friendly pattern uses defer to turn off a loading spinner. Without it, every early return would need its own isLoading = false line, and it’s easy to miss one. With defer, it’s impossible to forget.

    How swift defer Appears in Real iOS Code

    Let’s look at a realistic iOS example. This is a view model method that fetches a list of posts from an API. It uses defer to guarantee the loading state is always cleaned up, and wraps things in proper error handling.

    import SwiftUI
    
    class PostsViewModel: ObservableObject {
        @Published var posts: [Post] = []
        @Published var isLoading = false
        @Published var errorMessage: String?
    
        func fetchPosts() async {
            isLoading = true
    
            // This guarantee means we NEVER leave the spinner on by accident.
            // Every early return, every thrown error, every success path
            // will all hit this defer block before exiting.
            defer {
                isLoading = false
            }
    
            guard let url = URL(string: "https://api.example.com/posts") else {
                errorMessage = "Invalid URL"
                return // defer runs here
            }
    
            do {
                let (data, _) = try await URLSession.shared.data(from: url)
                let decoded = try JSONDecoder().decode([Post].self, from: data)
                posts = decoded
            } catch {
                errorMessage = error.localizedDescription
            }
            // defer runs here at the end of the function normally too
        }
    }

    Here’s why each decision was made:

    Setting isLoading = true before the defer is intentional. The spinner turns on immediately. The defer turns it back off at the very end, no matter what path the function takes.

    The guard statement for the URL can return early if the URL is invalid. Without defer, you’d need to add isLoading = false inside that guard’s else block. With defer, you don’t.

    The do/catch block handles any errors from the network request or JSON decoding. Whether it throws or succeeds, the defer block still fires on the way out.

    This is the kind of code you’ll write regularly in real iOS apps. Any time you have a boolean flag that needs to be reset, or a resource that needs to be released, and there are multiple ways the function might exit, defer is the clean solution.

    When Should I Use defer in Swift?

    Defer is not something you’ll need in every function. But it’s exactly right in several common situations. Here’s a quick map of when it earns its place:

    • 🔒

      Locking and unlocking

      If you lock something at the start of a function (like a thread lock or a database write lock), defer ensures you unlock it before returning. Forgetting to unlock is a classic source of freezing apps and deadlocks.

    • 📂

      Opening and closing files or connections

      Whenever you open something that needs to be closed — a file handle, a database connection, a network session — put the close call in a defer right after the open. That way they always stay paired together.

    • Loading states and spinners

      Whenever you show a loading indicator at the start of a function, defer is the cleanest way to make sure it always gets hidden. No matter how the function exits, the spinner turns off.

    • 🪵

      Logging and analytics

      Sometimes you want to log how a function ended regardless of the outcome. Putting the log call in a defer means it always fires, and because defer captures variables by reference, it can even log the final state of your data.

    When NOT to use defer: If your function has only one exit point and no cleanup needed, defer adds complexity without any benefit. Keep it for situations where cleanup must happen regardless of how the function exits. Overusing it can make code harder to read.

    Common Mistakes to Avoid

    Mistake 1: Expecting defer to change the return value

    You might assume that if you modify a variable inside a defer block, that modification will change what the function returns. It won’t.

    func getScore() -> Int {
        var score = 10
        defer {
            score = 99 // this does NOT change the return value
        }
        return score // returns 10, not 99
    }
    
    print(getScore()) // prints 10

    The return value is captured when the return statement is reached. The defer runs after that, so any changes it makes to the variable are too late to affect what gets returned.

    Key rule: Use defer for side effects (logging, closing things, resetting flags) — not for computing return values.

    Mistake 2: Using return inside a defer block

    You can’t use return, break, or continue inside a defer block. Swift won’t let you exit the current scope from inside the block that’s supposed to run during that exit. It would create a logical paradox. Xcode will give you a compile error if you try.

    Mistake 3: Thinking defer runs at the point where you write it

    New Swift developers sometimes read code with defer and think the deferred code runs right there, in order with everything else. It doesn’t. The deferred code is registered when that line is hit, but it doesn’t execute until the scope is exiting.

    func confusingOrder() {
        defer { print("B") } // registered first, runs last
        print("A")            // runs immediately
    }
    // prints: A, then B

    This is the concept that takes most beginners the longest to internalize. The way to think about it: defer is a promise, not an action. You’re promising Swift “do this before leaving.” The promise is made when you write it, but fulfilled when you exit.

    Mistake 4: Writing complex logic inside defer

    Defer blocks should be short and focused. If you find yourself writing ten lines of business logic inside a defer, that’s a sign something has gone wrong. A defer block that’s hard to read defeats the purpose of using it. Keep deferred code to a single action or a short cleanup sequence at most.

    Quick rule of thumb: If you can’t describe what your defer does in five words, it might be doing too much.

    Quick Reference

    Here’s a scannable summary of everything you’ve learned about defer in Swift:

    Syntax / PatternWhat It Does
    defer { }Schedules the code inside to run right before the current scope exits
    defer inside a functionRuns before the function returns, regardless of which return or throw is hit
    defer with guardRuns even if a guard triggers an early return — the most common real-world pattern
    defer with throwsRuns even if an error is thrown, equivalent to try/finally in other languages
    Multiple defers in one scopeRun in reverse order (LIFO) — last written, first executed
    defer inside a loopRuns at the end of each loop iteration, not just at the end of the loop
    defer inside a do blockRuns when the do block ends, not when the function ends
    Variable capture in deferSees the variable’s value at exit time, not at the time defer was written
    Cannot return inside deferYou can’t use return, break, or continue inside a defer block
    Cannot change return valueModifying a variable in defer does not change what the function returns

    Using AI to Go Deeper with swift defer

    AI coding assistants like Claude, Copilot, and ChatGPT are part of how modern developers work. Knowing how to prompt them well is itself a skill. Here are three categories of prompts designed to help you actually understand defer, not just copy code that uses it.

    Deepen Your Understanding Use AI as a tutor — no code generation required
    Explain the Swift defer keyword to me like I’m a beginner. Use a real-world analogy, then walk me through the simplest possible code example line by line so I understand the execution order.
    I think I understand Swift defer but I’m not 100% sure. Can you quiz me on it? Ask me one question at a time about what the code will print, tell me if I’m right or wrong, and explain why.
    Build a Practice Example Get learning-focused code with comments that explain the why
    Write a Swift function that uses defer in a realistic iOS context, like loading data or updating UI. Add a comment on every line explaining what it does and why — write the comments for a beginner who is seeing defer for the first time.
    Give me three Swift examples that use defer, each one slightly more complex than the last. The first should be a basic function with a single defer. The second should involve a guard statement and an early return. The third should involve error handling with throws. Add inline comments throughout so I can follow the execution order.
    Audit Your Own Code Use AI to review your code — you stay in the driver’s seat
    Here’s some Swift code I wrote: [paste your code]. Are there any places where I should be using defer but I’m not? For each suggestion, explain why defer would be better than what I currently have.
    Review this Swift code for how I’m using defer: [paste your code]. Am I using it correctly? Are there any problems with my execution order or any cases where my cleanup code might not run? Explain the issues before suggesting changes.
    Important: If AI generates code for you, make sure you can explain every single line before you move on. The goal isn’t to have working code — it’s to understand how that code works. If you can’t explain it, ask the AI to walk you through it until you can.

    Summary

    Here’s a quick recap of what you learned about Swift defer:

    • The defer keyword schedules code to run right before the current scope exits, no matter how it exits
    • It’s perfect for cleanup tasks like resetting loading states, closing connections, and unlocking resources
    • Defer is especially useful when a function has multiple guard statements with early returns
    • Multiple defer blocks in the same scope run in reverse order (last written, first executed)
    • Defer captures variables by reference, so it sees the variable’s final value at exit time
    • You cannot use return, break, or continue inside a defer block
    • Modifying a variable inside defer does not change the function’s return value
    • Keep defer blocks short and focused on a single cleanup task

    The best way to get comfortable with defer is to try it in your own code. Find a function you’ve written that has multiple guard statements and cleanup code repeated in several places. Refactor it to use defer and watch how much cleaner it gets.

    Once you’re comfortable with defer, a great next step is going deeper on Swift error handling with do/catch and throws. The two concepts go hand in hand in real iOS apps, and defer is one of the most useful tools for writing clean throwing functions.



    Get started for free

    Join over 2,000+ students actively learning with CodeWithChris