Back to blog

Sunday, August 16, 2026

Why Does My nil Interface Not Equal nil?

Title card for 'Why Does My nil Interface Not Equal nil?' with a short code example

Why Does My nil Interface Not Equal nil? (Go's Most Famous Gotcha)

An interface value is a pair: (dynamic type, dynamic value). It equals nil only when both parts are nil. A nil *T stored in an error (or any interface) still has a type: *T. The pair is (*T, nil), which is not a nil interface. err != nil is true. fmt.Println(err) prints <nil>. That is the bug.

The failure

This function "succeeds." The caller thinks it failed.

package main

import "fmt"

type MyError struct{ msg string }

func (e *MyError) Error() string { return e.msg }

func mightFail() error {
    var err *MyError // nil pointer
    return err       // boxed as error: type=*MyError, value=nil
}

func main() {
    err := mightFail()
    fmt.Println(err)        // <nil>
    fmt.Println(err == nil) // false
    if err != nil {
        fmt.Println("treating success as failure")
    }
}

The same trap with io.Writer:

func writeAll(w io.Writer, p []byte) {
    if w != nil {
        w.Write(p) // panics if w holds a nil *bytes.Buffer
    }
}

func main() {
    var buf *bytes.Buffer // nil
    writeAll(buf, []byte("x"))
}

var w io.Writer is a real nil interface. writeAll(w, p) is safe. Passing the nil *bytes.Buffer is not: the interface is non-nil, Write is called, the receiver is nil.

Why this happens

An interface value is a type and value pair. A nil *MyError stored in an error has type *MyError, so err == nil is false

var e error              →  (type=nil,  value=nil)     e == nil  → true
var p *MyError = nil
e = p                    →  (type=*MyError, value=nil) e == nil  → false
return nil               →  (type=nil,  value=nil)

Assignment into an interface always fills the type slot from the static type of the value. return err where err is *MyError copies that type in, even when the pointer is nil.

fmt prints <nil> because the value is nil. Your if err != nil looks at the interface, which still has a type.

Method calls on a typed-nil interface dispatch. The panic, if any, is inside the method when it dereferences the nil receiver, not at if w != nil.

The fix

Return untyped nil when there is no error. Keep the result variable as error, not *MyError.

func mightFail() error {
    return nil
}

func mightFail2() (err error) {
    var concrete *MyError
    if bad {
        concrete = &MyError{msg: "nope"}
        return concrete
    }
    return nil // not return concrete
}

If you must inspect a boxed pointer, assert it:

if err != nil {
    if e, ok := err.(*MyError); ok && e == nil {
        // typed nil: treat as success, or don't produce this value
    }
}

Do not produce that value. return nil on the interface type is the rule. Same for any interface you return: if the concrete pointer is nil, do not assign it into the interface.