Sunday, August 16, 2026
How to Check If a Type Implements an Interface at Compile Time in Go
Posted by

There is no implements keyword. Satisfaction is implicit: the compiler checks when you assign to an interface. If your type is only used through plugins, JSON, or http.Handler registration in another package, that assignment may never happen in your tree, until production. Force it with a blank assignment. The compiler prints the missing method. The line is deleted after type-checking. It does not run.
The failure
MyHandler is meant to be an http.Handler. Nobody assigns it in this package. You rename ServeHTTP and the library still builds. The process dies when a request hits it.
type MyHandler struct{}
func (h *MyHandler) ServeHttp(w http.ResponseWriter, r *http.Request) {
// wrong name: ServeHttp vs ServeHTTP
}
The compiler is silent. Implicit satisfaction has nothing to check.
Why this happens
Go will not let a type declare a list of interfaces it "is." That is deliberate: the consumer owns the interface. The cost is a gap. If no assignment exists, method-set drift is invisible.
A dummy assignment closes the gap. var _ I = x is a compile-time proof that x's type is in I's method set.
Pointer vs value still matters. Methods on *T do not put T in the set. Checking var _ I = T{} when Write is on *T fails, correctly. Checking var _ I = (*T)(nil) when you only ever pass T values passes while call sites fail. Match the type you actually hand to callers.
(*T)(nil) is a typed nil pointer. It is not the nil-interface gotcha. You are not boxing it as a live error. You are only asking the type checker whether *T may be stored in I.
The fix
Put the check next to the type, at package scope.
type ConsoleLogger struct{}
func (c *ConsoleLogger) Write(p []byte) (int, error) {
return os.Stdout.Write(p)
}
var _ io.Writer = (*ConsoleLogger)(nil)
Value receivers:
type Point struct{ X, Y int }
func (p Point) String() string {
return fmt.Sprintf("(%d,%d)", p.X, p.Y)
}
var _ fmt.Stringer = Point{}
If you support both, check both:
var _ fmt.Stringer = Point{}
var _ fmt.Stringer = (*Point)(nil)
A failed check looks like:
cannot use (*ConsoleLogger)(nil) (type *ConsoleLogger) as type io.Writer in assignment:
*ConsoleLogger does not implement io.Writer (missing method Write)
That is the message you want at go build, not in a production stack. Use it on exported types that promise a stdlib or public interface (http.Handler, io.Reader, error, json.Marshaler). Skip it for a one-off local interface used only as a function parameter, the parameter already is the check.