Skip to content
Go Basics: Avoid Typed-Nil Error Surprises

Go Basics: Avoid Typed-Nil Error Surprises

October 31, 2026

A helper can accidentally return a non-nil error even when its pointer variable is nil. Inspect the interface’s dynamic type, then return an explicit nil on the success path.

Prepare a scratch directory

Tested on Linux amd64 with Go 1.26.10. Install Go and make go available on PATH. You also need Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. The example uses only the standard library and needs no module or external dependency.

Keep the same terminal open. Stop if setup fails, before saving the source:

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

Run the example

Save this as interface-nil-demo.go:

package main

import "fmt"

type ValidationError struct{ Field string }

func (e *ValidationError) Error() string {
	if e == nil {
		return "nil validation pointer"
	}
	return "invalid field: " + e.Field
}
func accidental() error {
	var detail *ValidationError
	return detail
}
func validate(ok bool) error {
	if ok {
		return nil
	}
	return &ValidationError{Field: "port"}
}
func main() {
	err := accidental()
	fmt.Println("Typed nil equals nil:", err == nil)
	fmt.Printf("Dynamic type: %T\n", err)
	fmt.Println("Success equals nil:", validate(true) == nil)
	fmt.Println("Failure:", validate(false))
}

Run it:

go run interface-nil-demo.go

Observed output:

Typed nil equals nil: false
Dynamic type: *main.ValidationError
Success equals nil: true
Failure: invalid field: port

Read the result

The interface returned by accidental holds a dynamic type (*main.ValidationError) and a nil pointer value. It therefore differs from a nil interface, which has neither a dynamic type nor a value. The explicit return nil in validate produces the expected success result.

Apply it carefully

A method on a nil pointer can run, but dereferencing its receiver without a check will panic. The nil-safe Error method here makes the example inspectable; it does not make returning typed nil errors a good API design. Prefer functions returning error with explicit success and failure branches.

The official Go reference documents the behavior used here.

Clean up

Remove the saved source, return to the starting directory, and remove the empty practice directory. go run does not leave an executable here; Go manages its build cache separately.

rm -- interface-nil-demo.go
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

If the directory is not empty, inspect its remaining contents before removing anything else.

Acknowledgements

This post was created with assistance from AI.

Last updated on