Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

defer, panic & recover
GO

defer, panic & recover

Practice questions covering LIFO defer execution, panic propagation, recover, and Go's error and control-flow behavior.

1. What will be printed by the following code using defer?

func printVal() {
    x := 10
    defer fmt.Println(x)
    x = 20
}

Output: 10

The critical rule of defer: arguments are evaluated when the defer statement executes, not when the deferred function runs.

The line defer fmt.Println(x) is read while x is still 10. The argument x — the value 10 — is captured at that moment and stored. Later, when the function returns and the deferred call actually runs, it prints the stored 10. The assignment x = 20 after the defer changes the variable, but the deferred call already has its argument.

So the output is 10.

Contrast with a closure:

defer func() { fmt.Println(x) }()

A closure captures the variable, not the value. In that version, the deferred function reads x at return time — it would print 20. The distinction — value captured vs. variable captured — is the whole question.

The interview answer: 10, because deferred arguments are evaluated at defer time.

Answer:

10

The critical rule of defer: arguments are evaluated when the defer statement executes, not when the deferred function runs.

The line defer fmt.Println(x) is read while x is still 10. The argument x — the value 10 — is captured at that moment and stored. Later, when the function returns and the deferred call actually runs, it prints the stored 10. The assignment x = 20 after the defer changes the variable, but the deferred call already has its argument.

So the output is 10.

Contrast with a closure:

defer func() { fmt.Println(x) }()

A closure captures the variable, not the value. In that version, the deferred function reads x at return time — it would print 20. The distinction — value captured vs. variable captured — is the whole question.

The interview answer: 10, because deferred arguments are evaluated at defer time.

2. What is the execution order of multiple deferred functions inside the same function scope?

Answer: LIFO — Last In, First Out. The last deferred call runs first.

Each defer statement pushes its function onto a per-function stack. When the surrounding function returns, the runtime pops that stack, executing deferred calls in reverse order — the most recently deferred runs first.

This is deliberate, and it supports the classic cleanup pattern:

func copyFile() {
    f, _ := os.Open(src)
    defer f.Close()
    g, _ := os.Create(dst)
    defer g.Close()
    // ...
}

Resources acquired later are released first — g closes before f — which is exactly the right order for dependent resources (you close the destination before the source, matching acquisition in reverse). The LIFO ordering is guaranteed, not incidental.

The interview answer: multiple defers run in LIFO order, like a stack being popped.

Answer:

LIFO — Last In, First Out. The last deferred call runs first.

Each defer statement pushes its function onto a per-function stack. When the surrounding function returns, the runtime pops that stack, executing deferred calls in reverse order — the most recently deferred runs first.

This is deliberate, and it supports the classic cleanup pattern:

func copyFile() {
    f, _ := os.Open(src)
    defer f.Close()
    g, _ := os.Create(dst)
    defer g.Close()
    // ...
}

Resources acquired later are released first — g closes before f — which is exactly the right order for dependent resources (you close the destination before the source, matching acquisition in reverse). The LIFO ordering is guaranteed, not incidental.

The interview answer: multiple defers run in LIFO order, like a stack being popped.

3. What happens when a panic occurs and is NOT handled by recover()?

Answer: The panic unwinds the goroutine’s stack, runs all deferred functions, prints the panic message with a stack trace, and terminates the program.

A panic is Go’s mechanism for “something unrecoverably wrong happened at runtime.” The sequence when it’s not recovered:

  1. The current function stops executing and starts unwinding — control returns up the call stack.
  2. Each deferred function along the way still runs (defers execute during unwinding). This is why defer cleanup is reliable even under panics.
  3. If no recover() intercepts it, the runtime prints the panic value and a stack trace, then exits the process with a non-zero status.

Key points: the panic is not converted into an error value returned to main — it kills the process. And the unwinding is per-goroutine: a panic in one goroutine that’s not recovered crashes the whole program (unless that goroutine recovers), because there’s nowhere else to go.

The interview answer: unhandled panics run defers, print the panic and stack trace, and terminate execution.

Answer:

The panic unwinds the goroutine’s stack, runs all deferred functions, prints the panic message with a stack trace, and terminates the program.

A panic is Go’s mechanism for “something unrecoverably wrong happened at runtime.” The sequence when it’s not recovered:

  1. The current function stops executing and starts unwinding — control returns up the call stack.
  2. Each deferred function along the way still runs (defers execute during unwinding). This is why defer cleanup is reliable even under panics.
  3. If no recover() intercepts it, the runtime prints the panic value and a stack trace, then exits the process with a non-zero status.

Key points: the panic is not converted into an error value returned to main — it kills the process. And the unwinding is per-goroutine: a panic in one goroutine that’s not recovered crashes the whole program (unless that goroutine recovers), because there’s nowhere else to go.

The interview answer: unhandled panics run defers, print the panic and stack trace, and terminate execution.

4. Where can recover() be called to successfully intercept a panic?

Answer: Exclusively inside a deferred function.

recover() only works when called directly from a deferred function. The reason is timing: a panic stops normal execution, so the only code that runs afterward is deferred code. A direct call to recover() during normal execution (not from a defer) always returns nil — there’s no panic in flight at that moment.

The canonical pattern:

func safe() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("recovered:", r)
        }
    }()
    panic("boom")   // intercepted by the deferred recover
}

Two constraints make this subtle:

  • recover() must be called within the deferred function (directly, not from a helper it calls).
  • It only catches a panic happening in the same goroutine.

The interview answer: recover() works only inside a deferred function.

Answer:

Exclusively inside a deferred function.

recover() only works when called directly from a deferred function. The reason is timing: a panic stops normal execution, so the only code that runs afterward is deferred code. A direct call to recover() during normal execution (not from a defer) always returns nil — there’s no panic in flight at that moment.

The canonical pattern:

func safe() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("recovered:", r)
        }
    }()
    panic("boom")   // intercepted by the deferred recover
}

Two constraints make this subtle:

  • recover() must be called within the deferred function (directly, not from a helper it calls).
  • It only catches a panic happening in the same goroutine.

The interview answer: recover() works only inside a deferred function.

My Private Notes

Notes are auto-saved locally to this device.