Go Nil Interface Method Call — Panic at Runtime
Fix "invalid memory address or nil pointer dereference" from calling a method on a nil interface by returning typed nils only when callers expect them.
Symptoms
- panic: runtime error: invalid memory address or nil pointer dereference
- The stack points at a method call on an interface value
- The interface "is not nil" in logs yet the method panics
Possible causes
- A typed nil (*T)(nil) is stored in an interface, so the interface itself is non-nil but the method dereferences nil
- A function returns an interface without a valid implementation and the caller calls a method
- A dependency is injected as nil and used without a guard
Diagnosis steps
Print the interface with type information
fmt.Printf("%T %v\n", iface, iface)Interpretation
A non-nil type with a nil value shows a typed nil, explaining why the "not nil" check passed but the call panicked.
Reproduce the nil interface call
go run - <<'EOF'
package main
import "fmt"
type I interface{ Do() }
type T struct{}
func (T) Do(){ fmt.Println("do") }
func bad() I { var p *T; return p } // typed nil
func main(){ i := bad(); if i != nil { i.Do() } }
EOFInterpretation
This minimal program panics despite the nil check, proving the typed-nil trap.
Root cause
An interface value is nil only when both its type and value are nil. Returning a typed nil pointer assigns a concrete type to the interface, making the interface non-nil while the underlying receiver is still nil, so the method call dereferences nil.
Fix
Return a true nil interface (return nil, not a typed nil) at the point a dependency is absent, and use the comma-ok or a small sentinel implementation instead of comparing interfaces to nil.
Risk note
Comparing an interface to a typed nil constant compiles but never matches, so the guard silently fails.
Verify the fix
Rerun the minimal program and confirm it no longer panics and prints the guard branch. Add a test asserting the constructor returns a nil interface on the absent path.
Symptoms
The error “Go Nil Interface Method Call — Panic at Runtime” points at a specific failure mode. This guide documents the symptoms you can observe, the commands that isolate the cause, the root cause, the fix, and the checks that prove the fix works.
Observed symptoms include:
- panic: runtime error: invalid memory address or nil pointer dereference
- The stack points at a method call on an interface value
- The interface “is not nil” in logs yet the method panics
Reproduce and Diagnose
Reproduce the failure in isolation before changing any code. Work through the checks below in order; each one narrows the search space.
Step 1: Print the interface with type information
fmt.Printf("%T %v\n", iface, iface)
A non-nil type with a nil value shows a typed nil, explaining why the “not nil” check passed but the call panicked.
Step 2: Reproduce the nil interface call
go run - <<'EOF'
package main
import "fmt"
type I interface{ Do() }
type T struct{}
func (T) Do(){ fmt.Println("do") }
func bad() I { var p *T; return p } // typed nil
func main(){ i := bad(); if i != nil { i.Do() } }
EOF
This minimal program panics despite the nil check, proving the typed-nil trap.
Likely contributors, in order of frequency:
- A typed nil (*T)(nil) is stored in an interface, so the interface itself is non-nil but the method dereferences nil
- A function returns an interface without a valid implementation and the caller calls a method
- A dependency is injected as nil and used without a guard
Root Cause
An interface value is nil only when both its type and value are nil. Returning a typed nil pointer assigns a concrete type to the interface, making the interface non-nil while the underlying receiver is still nil, so the method call dereferences nil.
The Fix
Return a true nil interface (return nil, not a typed nil) at the point a dependency is absent, and use the comma-ok or a small sentinel implementation instead of comparing interfaces to nil.
Watch out: Comparing an interface to a typed nil constant compiles but never matches, so the guard silently fails.
type Store interface{ Save() error }
func newStore(enabled bool) Store {
if !enabled {
return nil // true nil interface, not (*kvStore)(nil)
}
return &kvStore{}
}
s := newStore(false)
if s != nil { // correct: s is a genuine nil interface
_ = s.Save()
}
Verify and Prevent
Rerun the minimal program and confirm it no longer panics and prints the guard branch. Add a test asserting the constructor returns a nil interface on the absent path.
Once verified, add a regression test that reproduces the original symptom so the failure cannot silently return.
This class of failure is usually environmental rather than pure logic, so capture the exact command, the environment variables, and the working directory in your bug report. Reproduce it in the smallest possible script, then widen the scope only once the minimal case is understood. A short written checklist for this failure mode saves hours the next time it appears.