The shape of the bug
It is almost always this, and once you have seen it once you spot it by the
signature alone.
type ValidationError struct{ Field string }
func (e *ValidationError) Error() string { return "invalid " + e.Field }
func validate(input string) *ValidationError {
if input == "" {
return &ValidationError{Field: "input"}
}
return nil
}
func handle(input string) error {
return validate(input)
}
func main() {
if err := handle("fine"); err != nil {
}
}
Nothing here is a compiler bug or an obscure corner. Every line follows from
what an interface value is.
An interface value is two words
A variable of a concrete pointer type holds one thing: an address, which may be
zero. A variable of an interface type holds two: a pointer to type information,
and a pointer to the data. The interface is nil only when both are unset —
which is to say, when nothing has ever been assigned to it.
Assigning a nil *ValidationError sets the first word to *ValidationError and
the second to zero. The type word is populated, so the interface is not nil. It
is an interface that definitely holds something, and the something it holds
happens to be a nil pointer.
That is why err != nil is true. The comparison is asking about the interface,
not about the pointer inside it, and the interface is genuinely not empty.
Two consequences follow. Calling a method on it does not immediately panic —
method dispatch works fine, because the type word tells the runtime which
Error to call — and it panics only when that method dereferences its nil
receiver. And a method with a value receiver on a nil pointer panics before the
body runs at all, while a method that never touches the receiver works
perfectly, which is why this bug can lurk for months in code whose error type
happens to have a constant message.
Fix the signature, not the call site
The tempting fix is defensive and wrong:
if err := handle(input); err != nil && err.(*ValidationError) != nil {
The correct fix is to stop producing the value. A function that reports failure
should return error, not a concrete pointer type:
func validate(input string) error {
if input == "" {
return &ValidationError{Field: "input"}
}
return nil
}
The rule generalises: never return a concrete error type from a function
whose result is going to be assigned to error. Linters catch the common
form, and knowing why the linter is right is what an interviewer is after.
If you genuinely need a concrete type — a constructor returning
*ValidationError for a caller that will inspect its fields — then the caller
must convert deliberately rather than assigning through an error return, and
the function must not be the one filling in an error slot.
The same mistake wearing other clothes
Go has a small family of these, all arising from a value being more structured
than it looks.
A nil map reads fine and returns the zero value, and panics on write. So a
struct with an uninitialised map field works in tests that only read it.
A nil slice appends fine, because append allocates when there is no
backing array. So nil and empty behave identically for most purposes, and
differ when you compare them or serialise them to JSON, where one becomes
null and the other [].
A nil channel blocks forever on both send and receive, which is either a
deadlock or, in a select, a deliberately disabled case — one of the few
places where the behaviour is a feature.
The unifying point is that Go's zero values are usable, which is a genuine
strength, and the price is that "not initialised" and "deliberately empty" are
frequently the same bit pattern.
Why this is on the interview
It is a good question because it cannot be answered from documentation you
skimmed. A candidate who says "the interface has a type word" has a model of the
runtime; a candidate who says "you should check for nil differently" has met the
symptom and not the cause. It also has a real production shape — the wrapped
error that is always non-nil turns every success into a logged failure, and the
handler that panics on a nil receiver takes down a request that succeeded.
An interface is nil only when it holds no type. A nil pointer is something,
and an interface holding something is not nothing.
© 2026 Preptima. Originally published at preptima.com (opens in a new tab).