Lesson 7 · Methods, interfaces & generics
The nil-interface gotcha
When err != nil is true and there's no error. The famous trap.
Your win: explain why a non-nil interface can hold a nil pointer, recognise the "typed nil error" bug, and state the fix. This is the most-asked Go interview gotcha — nail it and you sound like you understand the runtime.
An interface is a (type, value) pair
Under the hood, an interface value holds two words: a dynamic type and a value.1 The rule everything hinges on:
nil only when both its type and its
value are nil. If the type slot is set — even with a nil value — the interface is
not nil.
The bug: returning a typed nil as an error
func doThing() error {
var e *MyError = nil // a nil *MyError pointer
return e // ⚠️ assigned into the error interface
}
err := doThing()
if err != nil { // ✅ TRUE — even though "there is no error"!
// the interface holds (type=*MyError, value=nil) → type slot set → not nil
}
The classic form: a function declares a concrete error type, does its work, and
return errVar where errVar is a nil pointer. Callers'
if err != nil fires anyway, and everyone loses an afternoon.2
The fix — and why your repo is already safe
Return the literal nil for the interface on success;
never return a typed nil pointer where an interface is expected. Our handlers do this
naturally — success paths return an untyped nil:
return resp, nil // ✅ literal nil → interface truly nil
return nil, status.Error(codes.Internal, ...) // error path returns a real error
error directly and hand back a
bare nil on success — never a *someError that happens to be
nil. The trap bites when you have a helper returning a concrete pointer type
and you funnel it into an error return without a nil check. Keep the
error variable typed as error, not *MyError.
var w io.Writer = (*bytes.Buffer)(nil) is non-nil. Whenever you assign a
concrete pointer into an interface, the interface carries that type forever.
Go FAQ — "Why is my nil error value not equal to nil?"
The official explanation, straight from the source. Then 100 Go Mistakes covers typed-nil receivers in depth.
→ go.dev/doc/faq#nil_error
→ 100 Go Mistakes (nil receivers)
Check yourself (from memory)
Q1. An interface value equals nil only when…
Q2. A func returns a nil *MyErr as an error. The caller's err != nil is…
*MyErr, nil) — type
set, so non-nil. The bug that eats afternoons.
Q3. To avoid the trap, a function returning error should…
nil — our
repo's return resp, nil. Never funnel a concrete nil pointer into it.
return e (a nil *MyError) breaks if err != nil.error interface. Assigning a
nil *MyError gives the interface a non-nil type slot
(*MyError) with a nil value. An interface is nil only if both
are nil, so it's non-nil — the caller's if err != nil fires. Fix: return
the literal nil, and keep the variable typed as error, not
*MyError.1. The Go Blog — The Laws of Reflection (interface = type + value).