- Book: The Complete Guide to Go Programming
- Also by me: Hexagonal Architecture in Go — the companion book in the Thinking in Go series
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
You add a method to an interface. Or you rename one. The build
passes. Tests pass. You ship. Two days later a different package
that was supposed to implement that interface stops compiling, or
worse, keeps compiling because it never used the type through the
interface. Somewhere a var handler Handler = myThing was the only
thing tying the concrete type to the contract, and you deleted it
during a refactor.
Go's implicit interfaces are one of the best things about the
language. A type satisfies an interface by having the right methods,
no implements keyword required. But implicit satisfaction has a
cost: nothing in the type's own file says "this must satisfy
Handler." The check only happens where the type gets assigned to
the interface. Move that assignment, and the guarantee moves with
it.
There is a one-line fix that has been idiomatic Go since before
modules existed, and most codebases still under-use it.
The line
var _ http.Handler = (*Router)(nil)
Read it left to right. Declare a package-level variable named _
(the blank identifier, so nothing is actually stored) of type
http.Handler, and assign it a nil pointer of type *Router.
The assignment is what does the work. Go has to check, at compile
time, that *Router satisfies http.Handler. If *Router is
missing a method, or has one with the wrong signature, the build
fails right there with a clear message:
cannot use (*Router)(nil) (value of type *Router)
as http.Handler value in variable declaration:
*Router does not implement http.Handler
(missing method ServeHTTP)
Because the variable is _, nothing is allocated and nothing is
kept. The (*Router)(nil) never gets dereferenced. It exists only
so the type checker has an assignment to verify. Zero runtime cost.
Why the nil pointer, not a value
You will see two shapes in the wild:
var _ Stringer = (*Buffer)(nil) // pointer receiver methods
var _ Stringer = Buffer{} // value receiver methods
Pick the one that matches how your methods are declared. If any
method in the interface has a pointer receiver, only *Buffer
satisfies the interface, so you need (*Buffer)(nil). If every
method has a value receiver, either works, and (*Buffer)(nil) is
still the safe default because it also covers the value case.
(*Buffer)(nil) is a typed nil pointer. It costs nothing to
construct and it can never accidentally run a method, since the
compiler only inspects its type. Buffer{} allocates a zero value.
For a small struct that is fine; for one with expensive zero-value
fields, prefer the nil pointer.
Where to put it
Placement is the part people get wrong. The assertion is only
useful if it lives where the break will be caught early and read by
the right person.
Next to the type that implements the interface
Put the line in the same file as the concrete type, right after the
type declaration:
package router
type Router struct {
routes map[string]http.HandlerFunc
}
var _ http.Handler = (*Router)(nil)
func (r *Router) ServeHTTP(
w http.ResponseWriter, req *http.Request,
) {
// ...
}
Now the contract is documented where a reader looks first. If
someone removes ServeHTTP or changes its signature, the package
that owns Router fails to build. The failure lands on the person
who broke it, not on a downstream consumer three repos away.
This is the default. Use it for every type that exists to satisfy a
specific interface.
In the package that defines the interface, for external types
Sometimes you own the interface and want to guarantee that a
third-party or standard-library type satisfies it. You can assert
that too, from your side:
package storage
// We rely on *os.File behaving as our ReadWriteCloser.
var _ ReadWriteCloser = (*os.File)(nil)
If a future Go version changed *os.File (it will not, but the
pattern generalizes to less stable dependencies), your build tells
you before your users find out.
In a test file when you do not want it in the binary
If you would rather keep the assertion out of production code, put
it in a _test.go file in the same package:
// router_assertions_test.go
package router
var _ http.Handler = (*Router)(nil)
The check still runs on every go test and every go vet. The
tradeoff: a plain go build of the package will not catch the
break, only the test build will. For most teams the in-file version
is better because CI and local go build both catch it. Reach for
the test-file version only when you have a reason to keep the
production file clean.
Grouping several assertions
When a type satisfies more than one interface, or a package has
several implementers, group them so they read as a manifest:
var (
_ io.Reader = (*Conn)(nil)
_ io.Writer = (*Conn)(nil)
_ io.Closer = (*Conn)(nil)
_ net.Conn = (*Conn)(nil)
)
Anyone opening the file sees every contract *Conn promises to
keep. This doubles as documentation that never drifts, because if
it drifts the build breaks.
What it does not do
Be honest about the boundary. This check verifies method sets. It
does not verify behavior. A *Router that satisfies http.Handler
by having a ServeHTTP method that panics on every request still
passes the assertion. The line guarantees the shape of the contract,
not that you honored its meaning. That is what your tests are for.
It also does not help with interfaces you satisfy dynamically, where
the concrete type is only known at runtime. For those, the
assignment already happens in real code and the compiler checks it
there.
Why this beats finding out later
Without the assertion, a broken interface implementation surfaces in
one of three ways, all worse than a local build error:
- A downstream package fails to compile, and the error points at the consumer, not the type that actually changed.
- The type is only ever used concretely, so nothing catches the break until someone finally tries to pass it as the interface — possibly months later.
- The mismatch is a typed-nil-through-interface trap, where the code compiles and panics at runtime instead.
var _ Iface = (*T)(nil) collapses all of that into a single line
the compiler checks on every build, in the file where the type
lives. It is the cheapest guardrail Go gives you. Add it to every
type whose whole reason for existing is to satisfy an interface, and
you will never again ship a type that quietly stopped fulfilling its
contract.
Interfaces are where Go's implicit design is both its best feature
and its sharpest edge. The Complete Guide to Go Programming digs
into how method sets, pointer receivers, and interface satisfaction
actually work in the type checker, so these assertions stop feeling
like magic. Hexagonal Architecture in Go shows where to draw the
interface boundaries in the first place, so the contracts you pin
with this one line are the ones that matter.

Top comments (0)