ProgrammingBackend Developer

Explain the features of deferred function parameters in Go: when and how are parameters evaluated for defer, and how does this differ from calling a function at the time defer is triggered?

Pass interviews with Hintsage AI assistant

Answer.

Defer is a unique mechanism in Go that allows a function to be executed after the completion of the current function, even in the event of a panic. Historically, this is analogous to on-exit constructs, but in Go, it is implemented as a stack of deferred calls. An important nuance is that the parameters of the deferred function are evaluated immediately when the defer statement is declared, not at the time of its actual invocation!

The issue — non-obvious behavior: one might expect that parameters are passed when defer is triggered, but this is not the case. This often leads to bugs when dealing with mutable or external variables.

The solution — always keep in mind that parameters are evaluated immediately, while the effect of the deferred call occurs later.

Example code:

func f() { x := 5 defer fmt.Println(x) x = 10 } // Prints: 5, not 10

Key features:

  • The values of parameters for deferred functions are computed at the time of the defer statement.
  • The deferred function always executes even during a panic (if the panic is not caught earlier).
  • If an anonymous function is declared in defer — it can access the current values of variables.

Trick questions.

What will the following code print? Why?

func main() { i := 0 defer fmt.Println(i) i = 1 }

Answer: will print 0. The argument for fmt.Println is preserved immediately at the declaration of defer.

Does changing a variable after the defer declaration affect its value passed to the function?

No, it does not affect — the evaluation occurs at the defer declaration:

defer fmt.Println(x) // Value of x is saved now, not later

Can you create a defer to output the last state of a variable?

Yes, using an anonymous function (closure):

defer func() { fmt.Println(x) }() // will capture the current value of x at the time of deferred call

Common mistakes and anti-patterns

  • Expecting that the parameter will be current at the time of the deferred call.
  • Using defer with parameters alongside mutable variables.
  • Confusing stacks of deferred calls without explicit dependency descriptions.

Real-life example

Negative case

The code is invoked like this:

var f *os.File // ... defer f.Close()

But f is assigned later, leading to a panic from a nil pointer call!

Pros:

  • Short notation if the variable is already initialized.

Cons:

  • If f == nil — panic.

Positive case

Wrapping the cleanup action through an anonymous deferred function with a check:

defer func() { if f != nil { f.Close() } }()

Pros:

  • Safety and absence of panics.

Cons:

  • Longer and more "noisy" notation.