Explain method sets — why doesn't a pointer-receiver method satisfy an interface when passed by value?

5 minadvancedgomethod-setsinterfacespointers

Quick Answer

A type's method set determines which interfaces it satisfies. For a type T, the method set of T includes only methods with a value receiver (func (t T) M()); the method set of *T includes both value-receiver and pointer-receiver methods (func (t *T) M()). This means if a type only has pointer-receiver methods, a plain value of that type (not a pointer to it) does not satisfy an interface requiring those methods — only *T does. This trips people up when they write var i MyInterface = MyStruct{} and get a compile error, when var i MyInterface = &MyStruct{} would have worked, because the interface's methods were defined with pointer receivers.

Detailed Answer

This is one of the trickier corners of Go's type system, and it's a very common interview question precisely because it trips up experienced developers too.

The rule, demonstrated

type Counter struct{ n int }

func (c *Counter) Increment() { c.n++ }  // pointer receiver

type Incrementer interface {
    Increment()
}

var i Incrementer = Counter{}    // compile error!
var i Incrementer = &Counter{}   // works

The error: Counter (value) does not implement Incrementer (Increment method has pointer receiver). Only *Counter has Increment in its method set, since Increment needs a pointer to actually mutate n.

Why the language works this way

A value-receiver method gets a copy of the receiver, so it can never mutate the original. That's safe to call on either a value or a pointer, since Go automatically takes the address for you when needed (c.Increment() where c is addressable). A pointer-receiver method genuinely needs the real address to mutate the original. There's no way to get a genuine address from a plain, non-addressable value stored in an interface, so the method set of T excludes pointer-receiver methods entirely, keeping the rule simple and safe.

The practical gotcha with interfaces specifically

counters := []Counter{{n: 1}, {n: 2}}
for _, c := range counters {
    var i Incrementer = c   // still fails to compile — c here is a Counter value
}

Even though c came from a slice you could take the address of, the interface conversion still requires the value itself to have the pointer method in its set, and a plain Counter value never does.

The rule of thumb

If any method on a type uses a pointer receiver, treat the whole type as "used via a pointer" everywhere. That includes when it needs to satisfy an interface, and it avoids this whole class of compile error.

Related Resources