Defer execution order

August 26, 2024

Let’s talk about some of the nuance surrounding defer that’s often lost, or simply forgotten.

Defer statements

Each time a “defer” statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred. That is, if the surrounding function returns through an explicit return statement, deferred functions are executed after any result parameters are set by that return statement but before the function returns to its caller. If a deferred function value evaluates to nil, execution panics when the function is invoked, not when the “defer” statement is executed.

Let’s take this point by point.

When execution encounters a deferred function:

“function value and parameters are evaluated as usual” … “but the actual function is not invoked”

This bites all of us when we’re learning. And some of us it continues to bite for years into the future!

But what does it mean, exactly?

Let’s illustrate with an example.

func foo() {
	var i int
	defer fmt.Println(i)
	i = 10
	fmt.Println("foo")
}

What do you think this code will print when executed?

Try it if you like, to see that it prints:

foo
0

Does this surprise you?

The arguments to the deferred fmt.Println function are evaluated at the time the defer is encountered during execution, and at that point i == 0… but the function itself isn’t executed until the surrounding foo function returns, so after foo is printed.

Now, what do you think this will print?

func foo() {
	var i int
	defer func(p *int) { fmt.Println(*p) }(&i)
	i = 10
	fmt.Println("foo")
}

In this case, we’re passing a pointer to i to the deferred function. The argument is still evaluated immediately, as before… but in this case, when foo exits and the anonymous deferred function is executed, the value pointed to by i has changed, and we get the following output:

foo
10

Try it.

The function to be deferred is also evaluated immediately, not upon function exit. To demonstrate:

func main() {
	defer bar()()
	fmt.Println("Hello, 世界")
}

func bar() func() {
	fmt.Println("a")
	return func() {
		fmt.Println("b")
	}
}

This will output the following:

a
Hello, 世界
b

And the final point: If the function being deferred evaluates to nil, it will panic, but after the function returns, not at the point that defer is encountered:

func main() {
	defer bar()()
	fmt.Println("Hello, 世界")
}

func bar() func() {
	fmt.Println("a")
	return nil
}

This code will panic after some output:

a
Hello, 世界
panic: runtime error: invalid memory address or nil pointer dereference

See for yourself

Quotes from The Go Programming Language Specification Language version go1.23 (June 13, 2024)


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Get daily content like this in your inbox!

Subscribe