A function returns x and a defer changes x afterwards. What does the caller receive, for a value, an inout, a class instance and a string being built?

27.2.1 · L2 · Interview

Predict first

What does this print?

func plain() -> Int {
    var x = 1
    defer { x += 10; print("defer sees", x) }
    return x
}
print("plain returned", plain())

func viaInout(_ v: inout Int) -> Int {
    defer { v += 100 }
    return v
}
var n = 1
print("inout returned", viaInout(&n), "n is now", n)

final class Box { var value = 1 }
func viaRef() -> Box {
    let b = Box()
    defer { b.value = 99 }
    return b
}
print("class returned", viaRef().value)

func build() -> String {
    var log = ""
    defer { log += "D" }
    log += "A"
    return log + "R"
}
print(build())
  • defer sees 11 / plain returned 1 / inout returned 1 n is now 101 / class returned 99 / AR
  • defer sees 11 / plain returned 11 / inout returned 101 n is now 101 / class returned 99 / ARD
  • defer sees 11 / plain returned 1 / inout returned 101 n is now 1 / class returned 1 / AR

Mental model. The courier is handed the sealed parcel first, and only then do you tidy the desk. Tidying cannot change what is in the parcel, but it can change the desk the courier can still see.

The return expression is evaluated first, the result is held, then the deferred blocks run, then the function actually returns. So for plain() the defer does see the updated x (11), but the caller still receives the 1 that was returned: an Int is a copy, and the copy was already made. The string example shows the same order: log + "R" is computed as “AR” before the defer appends “D”, so the caller gets “AR”.

What a defer can change is anything the caller can still see after the return: an inout parameter (the function returned 1, but n becomes 101 because the defer mutates the caller’s variable), or the contents of a reference: viaRef() returns the same Box instance, so the caller reads 99. Use this on purpose for cleanup that must happen after the value is produced (unlock a lock, close a file, end a signpost), and never rely on a defer to change a returned value type. If the order matters, make it explicit: compute into a local, do the cleanup, then return it.

The return value is computed before the defer runs; only things the caller can still see (inout, a shared reference) show the change.

Common traps

  • Mutating the returned variable in a defer and expecting the caller to see it. For value types the return value is already copied.
  • Forgetting that returning a class instance returns the reference. A defer that changes its properties is visible to the caller.

They might follow up with

  • What if the defer itself returns? A defer cannot return, throw out of its scope, or use break/continue to leave it; the compiler rejects those.
  • What is a good use of defer? Pairing acquire with release (locks, file handles, beginBackgroundTask) right next to the acquire so every exit path cleans up.

Related questions

  • 27.2.2 When does a defer inside a loop, a throwing function or an early-exit branch run, and when is it never…
  • 27.4.4 What is a reliable routine for answering “what does this print?” under interview pressure?
  • 1.8.4 What does defer guarantee? What is the order of multiple defers?

When does a defer inside a loop, a throwing function or an early-exit… →