Golang - Interfaces - Interface Values and the nil Trap

Publish date: 2025-09-24
Tags: <a href="https://programmercave.com/tags/Go/">Go</a>

Introduction

This is one of the most famous “gotchas” in Go and a classic interview question for assessing a deeper understanding of interfaces. To understand the trap, you first need to know what an interface value looks like in memory.


The Structure of an Interface Value

Think of an interface variable not as a single thing, but as a box containing two pieces of information:

  1. Type: A label that says, “Here’s the kind of thing I’m holding.” (e.g., *CustomError).
  2. Value: The actual data or value itself.

An interface variable is only truly nil when both the type label and the value inside the box are empty.

Analogy: An interface is like a gift box.

An Interface "Box" in Memory
+-----------------+
|      Type       | --> (The "gift tag" describing the concrete type)
+-----------------+
|      Value      | --> (The "gift" or the actual data)
+-----------------+

The nil Interface Trap

The trap happens when an interface box contains a nil value but has a non-nil type tag. The box itself is not considered nil, even though the value inside it is.

This can be very confusing because when you print it, it might look like nil, but when you check if err != nil, the result is true.

The Classic Example: Returning a nil Pointer as an error

Let’s imagine a function that can return a special kind of error, but sometimes there’s no error at all.

package main

import "fmt"

// CustomError is our special error type.
type CustomError struct {
	Message string
}

// This method makes *CustomError a valid `error`.
func (e *CustomError) Error() string {
	return e.Message
}

// This function tries to do something and might produce a CustomError.
func doSomething() *CustomError {
	// Let's pretend everything went fine. We want to signal "no error."
	// We do this by returning `nil` for the *CustomError type.
	return nil
}

// This function calls doSomething() and returns the result as a standard `error`.
func run() error {
	// doSomething() returns a value that is a `nil` pointer of type *CustomError.
	// When we return this value as an `error`, Go puts it in the interface "box."
	// The box gets the type tag *CustomError, but the value inside is nil.
	return doSomething()
}

func main() {
	err := run()

	// This is the trap! The program will enter this `if` block.
	// Why? Because `err` (the box) is not nil. It has a type tag on it.
	if err != nil {
		fmt.Println("Error occurred!")
		fmt.Printf("The value inside the box is: %v\n", err)
		fmt.Printf("But the box itself is not nil: %t\n", err != nil)
	} else {
		fmt.Println("Success!")
	}
}

Output:

Error occurred!
The value inside the box is: <nil>
But the box itself is not nil: true

What happened? (In simple steps)

  1. The doSomething function returns nil, but specifically, it’s a “nil of type *CustomError”.
  2. The run function takes this typed nil and puts it into an error interface variable.
  3. The Go runtime prepares the error interface “box”:
    • It attaches the type tag *CustomError to the box.
    • It puts the nil value inside the box.
  4. The box now has a tag, so the box itself is not nil.
  5. The check if err != nil asks, “Is the box itself empty?” The answer is no (it has a tag), so the condition is true, and the program incorrectly thinks there’s an error.

How to Avoid the Trap

The fix is to be very direct. If you mean to return “no error,” you must return a truly empty interface box, not a box with a tag and an empty value.

The Fix:

func runFixed() error {
	customErr := doSomething()

	// Explicitly check if the value is nil.
	if customErr == nil {
		// If it is, return a truly empty `error` interface (a nil box).
		return nil
	}

	// If it's not nil, return the box with the error inside.
	return customErr
}

func main() {
	err := runFixed()

	if err != nil {
		fmt.Println("Error occurred!", err)
	} else {
		// This now works as expected.
		fmt.Println("Success!")
	}
}

Output:

Success!

Interview Question: “What will if err != nil do if err is an interface holding a nil pointer of a concrete type? Why?”

Answer: “The check err != nil will be true. An interface value is only nil if both its internal type and value parts are nil. When it holds a nil pointer, its type part is set, which makes the interface itself non-nil. This is a common trap, especially when returning typed nil pointers as an error.”

Tags: <a href="https://programmercave.com/tags/Go/">Go</a>