Skip to Content
Course content

181: Common Go Interview Questions on Defer Semantics

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've been looking at Go interview prep, you'll notice that defer is a favorite topic. On the surface, it seems simple: "run this at the end of the function." But interviewers love it because there are three specific "gotchas" that trip up almost everyone who hasn't spent a few years in a production Go codebase. Let's explore these by actually breaking some code.

The Stack Effect

I remember the first time I used multiple defer statements; I assumed they ran in the order they were written. Let's test that theory. I'll write a quick function that pushes a few strings to a slice via deferred calls.

package main

import "fmt"

func main() {
    fmt.Println("Starting execution...")
    defer fmt.Println("First defer")
    defer fmt.Println("Second defer")
    defer fmt.Println("Third defer")
    fmt.Println("Ending execution...")
}

When I run this, I expect "First, Second, Third." But the output is actually:

Starting execution...
Ending execution...
Third defer
Second defer
First defer

Right. It's a stack (LIFO). Go pushes every defer call onto a stack and pops them off when the function returns. This is crucial when you're dealing with resources—like opening a file and then locking a mutex. You have to unlock the mutex before you close the file, so you lock first, then defer the unlock. The last thing you "set up" is the first thing that gets "torn down."

The Argument Evaluation Trap

Now, here is where it gets tricky. Let's see what happens when we pass a variable to a deferred function. I'll create a variable, defer a print of that variable, and then change the variable's value before the function exits.

func argumentTest() {
    i := 0
    defer fmt.Println("Deferred value:", i)
    i++
    fmt.Println("Final value:", i)
}

I'm betting the output will be Final value: 1, Deferred value: 1. Let's run it. Nope. It prints:

Final value: 1
Deferred value: 0

Why? Because Go evaluates the arguments to the deferred function immediately, not when the function actually executes. When I wrote defer fmt.Println("...", i), Go took the current value of i (which was 0) and saved it. The fact that I incremented i later didn't matter; the 0 was already "baked in."

If I actually wanted the final value of i, I'd have to wrap the logic in a closure (an anonymous function) so that the variable is accessed at the time of execution, not at the time of definition:

defer func() {
    fmt.Println("Deferred value:", i)
}()

Manipulating the Return Value

This is the "final boss" of defer questions. Can a deferred function change the value a function returns? Let's try it with a standard return first.

func tryChange() int {
    i := 0
    defer func() {
        i++
    }()
    return i
}

I'll run this, and it returns 0. I'm not surprised. The return i statement effectively does two things: it sets the return value to 0, and then it triggers the deferred functions. By the time i++ happens, the return value has already been decided.

But wait—Go has named return parameters. Let's see if that changes the game.

func tryChangeNamed() (result int) {
    defer func() {
        result++
    }()
    return 0
}

Now, when I run this, it returns 1. This is a powerful (and sometimes dangerous) pattern. Because the return variable is named result, it's essentially a local variable that persists throughout the function. The deferred closure captures a reference to result and modifies it after the return 0 statement has executed, but before the caller actually receives the value.

You'll see this a lot in production code for error handling. If a function returns (int, error), you can use a deferred closure to check if the error is nil and, if not, wrap that error with more context before it heads back up the call stack.




📋 Practical Task

Exercise: Fixing the Deferred Return Value Bug

You are reviewing a teammate's code for a custom rounding utility. The goal is to return a value, but if that value is negative, a defer block should "intercept" it and flip it to a positive number (absolute value). However, the current implementation is failing to modify the return value.

Current Broken Code:

package main

import "fmt"

func AbsoluteValue(n int) int {
    defer func() {
        if n < 0 {
            n = -n
        }
    }()
    return n
}

func main() {
    fmt.Println(AbsoluteValue(-50)) // Currently prints -50, should print 50
}

Your Task: Modify the AbsoluteValue function signature and the return statement so that the deferred closure successfully modifies the returned integer. Ensure the output of main() becomes 50.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.