In Go, interfaces are satisfied implicitly. Whether a type satisfies an interface depends on its method set—the collection of methods accessible on that type.
The rules are simple:
- Value type
T: method set contains only methods with value receivers - Pointer type
*T: method set contains methods with both value and pointer receivers
This asymmetry is intentional and explains most interface satisfaction behavior in Go.
An Example
Let’s use one type throughout to see how this works:
type Counter struct {
value int
}
// Value receiver
func (c Counter) GetValue() int {
return c.value
}
// Pointer receiver
func (c *Counter) Increment() {
c.value++
}Method sets:
Counterhas:{GetValue}*Counterhas:{GetValue, Increment}
Why This Design?
When you call a method on a value, Go makes a copy. If Increment() were available on Counter, it would modify the copy, not the original.
Conversely, when you have a pointer, Go can always dereference it to call value methods and use the pointer directly for pointer methods.
Interface Satisfaction
Now the interface rules make sense:
type Reader interface {
GetValue() int
}
type Modifier interface {
Increment()
}Which types satisfy which interfaces?
var r Reader
var m Modifier
c := Counter{value: 5}
r = c // ✓ Counter has GetValue
m = c // ✗ Counter doesn't have Increment
cp := &Counter{value: 5}
r = cp // ✓ *Counter has GetValue
m = cp // ✓ *Counter has IncrementThe compiler error says: Counter does not implement Modifier (Increment method has pointer receiver)
The Practical Rule
If any method you need has a pointer receiver, you must use a pointer to satisfy the interface.
type Incrementer interface {
Increment()
GetValue() int
}
func Process(inc Incrementer) {
inc.Increment()
fmt.Println(inc.GetValue())
}
func main() {
c := Counter{value: 5}
// Process(c) // ✗ Won't compile
Process(&c) // ✓ Works
}Summary
Method sets determine interface satisfaction in Go. The rule is simple but has consequences:
- Value types see only value-receiver methods
- Pointer types see all methods
- If you need pointer-receiver methods through an interface, use a pointer
Once you internalize this, Go’s type system becomes predictable and the compiler errors make sense.