Question
Understanding "does not implement interface" with Pointer Receivers in Go
Question
In Go, I often see compiler errors like this:
X does not implement Y (... method has a pointer receiver)
There are already several questions and answers about this error, but many of them focus on specific cases. I want a broader explanation.
What are the common situations that cause this error? How do method sets, interfaces, and pointer receivers interact in Go? What are the usual ways to avoid the problem, and how can I diagnose it when it happens?
Short Answer
By the end of this page, you will understand why a Go type may fail to implement an interface when one or more methods use pointer receivers. You will learn how Go method sets work for values and pointers, how interface assignment checks those method sets, what common mistakes cause this error, and how to choose the right fix in real code.
Concept
In Go, a type implements an interface implicitly. That means you do not write implements; instead, the compiler checks whether the type has all the methods required by the interface.
The key detail is that Go treats the method set of a value type differently from the method set of a pointer type.
Consider this type:
type User struct {
Name string
}
func (u User) Display() {
fmt.Println(u.Name)
}
func (u *User) Rename(name string) {
u.Name = name
}
There are two method sets here:
Userhas the methods with receiverUser*Userhas the methods with receiverUserand*User
So:
- A
Uservalue hasDisplay() - A
*Userpointer has bothDisplay()andRename()
If an interface requires Rename(), then User does not implement it, but *User does.
That is why this error appears.
Why this matters
This affects:
- assigning a value to an interface
- passing values into functions that accept interfaces
- returning interface values
- storing values in interface-typed fields, maps, or slices
Because interfaces are checked against method sets, whether you pass x or &x can change whether the code compiles.
The rule to remember
If a method has a pointer receiver, only the pointer type is guaranteed to implement an interface requiring that method.
In short:
Tgets methods declared onT*Tgets methods declared onTand*T- If the interface needs a pointer-receiver method, use
*T
This is the core reason behind the error.
Mental Model
Think of a value type and a pointer type as two workers with different tool belts.
- The value worker (
T) carries only the tools meant for plain values. - The pointer worker (
*T) carries all the value tools plus the pointer-only tools.
An interface is like a job description.
If the job requires a tool that exists only on the pointer worker's belt, you cannot assign the value worker to that job.
So if the interface says:
Read()Update()
and Update() is only defined on *T, then only *T is qualified for the job.
A plain T is missing part of the required tool set.
Syntax and Examples
Core syntax
Value receiver
func (t T) MethodName() {
// works on a copy of T
}
Pointer receiver
func (t *T) MethodName() {
// works on a pointer to T
}
Interface
type Doer interface {
Do()
}
Example 1: value receiver works for both T and *T
package main
import "fmt"
type Speaker interface {
Speak()
}
type Person struct {
Name string
}
func (p Person) Speak() {
fmt.Println("Hello,", p.Name)
}
func main() {
var s1 Speaker = Person{Name: "Ava"}
var s2 Speaker = &Person{Name: "Liam"}
s1.Speak()
s2.Speak()
}
Because Speak() has a value receiver, both Person and *Person satisfy Speaker.
Example 2: pointer receiver means only *T implements the interface
package main
import "fmt"
type Renamer interface {
Rename(string)
}
type Person struct {
Name string
}
func (p *Person) Rename(name string) {
p.Name = name
}
func main() {
p := Person{Name: "Ava"}
// var r Renamer = p // compile error
var r Renamer = &p // correct
r.Rename("Mia")
fmt.Println(p.Name)
}
The commented line fails because Person does not have Rename() in its method set. Only *Person does.
Example 3: mixed receivers
package main
import "fmt"
type User struct {
Name string
}
func (u User) Print() {
fmt.Println(u.Name)
}
func (u *User) SetName(name string) {
u.Name = name
}
type Printer interface {
Print()
}
type NameSetter interface {
SetName(string)
}
func main() {
u := User{Name: "Noah"}
var p Printer = u // OK
p.Print()
// var s NameSetter = u // compile error
var s NameSetter = &u // OK
s.SetName("Emma")
fmt.Println(u.Name)
}
This shows exactly why some interfaces work with a value while others require a pointer.
Step by Step Execution
Consider this example:
package main
import "fmt"
type Writer interface {
Write(string)
}
type Document struct {
Text string
}
func (d *Document) Write(s string) {
d.Text += s
}
func main() {
doc := Document{Text: "Hello"}
// var w Writer = doc // compile error
var w Writer = &doc
w.Write(" world")
fmt.Println(doc.Text)
}
Step by step
1. Define the interface
type Writer interface {
Write(string)
}
Any type assigned to Writer must have a Write(string) method in its method set.
2. Define the struct
type Document struct {
Text string
}
So far, Document has no methods.
3. Add a pointer receiver method
func (d *Document) Write(s string) {
d.Text += s
}
This method belongs to *Document, not Document.
Method sets now look like this:
Document: noWrite*Document: hasWrite
4. Create a value
doc := Document{Text: "Hello"}
doc is a value of type Document.
5. Try interface assignment
var w Writer = &doc
This works because &doc is a *Document, and *Document has Write(string).
If you wrote:
var w Writer = doc
it would fail, because Document does not implement Writer.
6. Call the interface method
w.Write(" world")
The interface holds a *Document, so it calls the pointer receiver method.
7. Check the result
fmt.Println(doc.Text)
Output:
Hello world
The original doc changed because the method worked through a pointer.
Real World Use Cases
Pointer receiver interface issues appear often in normal Go programs.
1. Mutating structs
If a method changes the receiver, it usually uses a pointer receiver.
func (c *Counter) Increment() {
c.Value++
}
If an interface requires Increment(), you must usually pass *Counter.
2. Avoiding large copies
For large structs, pointer receivers avoid copying data.
func (r *Report) Process() error {
// work on a large struct
return nil
}
If Process() is part of an interface, only *Report implements it.
3. Standard library patterns
Many standard library types are used through pointers because their methods mutate internal state.
Examples include writers, buffers, decoders, and encoders.
var b bytes.Buffer
// io.Writer is implemented by *bytes.Buffer when used through its pointer
4. Dependency injection
A service may depend on an interface, and a concrete implementation may use pointer receiver methods.
type Store interface {
Save(string) error
}
If FileStore.Save has a pointer receiver, inject *FileStore, not FileStore.
5. HTTP handlers and app state
Application structs often store shared state and use pointer receiver methods.
func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// use shared dependencies
}
In such cases, *App implements http.Handler. A plain App value may not.
Real Codebase Usage
In real projects, developers use a few common patterns to avoid pointer-receiver interface problems.
Guard your interface assignments
A common compile-time check is:
var _ io.Writer = (*MyWriter)(nil)
This confirms at compile time that *MyWriter implements io.Writer.
If you accidentally expect MyWriter to implement it, the mismatch becomes obvious.
Use pointer receivers consistently when methods mutate state
If one method needs a pointer receiver, many teams make all methods on that type pointer receivers for consistency.
func (s *Session) Start() {}
func (s *Session) Stop() {}
func (s *Session) Reset() {}
This reduces confusion about whether to use T or *T.
Accept interfaces, return concrete types
A common Go pattern is:
- functions accept interfaces
- constructors return concrete types, often pointers
func NewService() *Service {
return &Service{}
}
Then callers naturally pass the pointer where needed.
Dependency fields often store interface values
type App struct {
Store Store
}
When wiring the app:
app := App{Store: &FileStore{}}
If you accidentally write FileStore{} instead of &FileStore{}, you may get the pointer receiver error.
Validation and error handling
These issues often appear during initialization code, where implementations are assigned to interfaces. Good constructors and compile-time checks make the errors easier to spot.
Common Mistakes
1. Passing a value when the interface requires pointer methods
Broken code:
type Runner interface {
Run()
}
type Job struct{}
func (j *Job) Run() {}
func start(r Runner) {}
func main() {
j := Job{}
start(j) // compile error
}
Fix:
start(&j)
2. Assuming automatic method call rules also apply to interfaces
Go sometimes lets you call a pointer receiver method on an addressable value:
j := Job{}
j.Run() // allowed if j is addressable
Beginners often think that means j also implements the interface. It does not.
Method call convenience is not the same as interface method set rules.
3. Mixing value and pointer receivers without understanding the effect
func (u User) Print() {}
func (u *User) Save() {}
Now:
Userimplements interfaces needingPrint*Userimplements interfaces needingPrintand/orSave
This is legal, but can confuse readers if done without a reason.
4. Returning a value when callers expect an interface implemented by a pointer
Broken code:
type Closer interface {
Close() error
}
type File struct{}
func (f *File) Close() error { return nil }
func NewFile() File {
return File{}
}
If someone tries to use NewFile() as a Closer, it fails.
Better:
func NewFile() *File {
return &File{}
}
5. Storing the wrong form in interface fields
Broken code:
type Saver interface {
Save() error
}
type DB struct{}
func (d *DB) Save() error { return nil }
type App struct {
Saver Saver
}
func main() {
app := App{
Saver: DB{}, // compile error
}
_ = app
}
Fix:
app := App{
Saver: &DB{},
}
Comparisons
Value receivers vs pointer receivers
| Topic | Value receiver func (t T) | Pointer receiver func (t *T) |
|---|---|---|
| Method belongs to | T | *T |
Available on T method set | Yes | No |
Available on *T method set | Yes | Yes |
| Can modify original value | No, works on a copy | Yes |
| Copies receiver data | Usually yes | No extra struct copy |
Interface implemented by T | Yes, for this method | No |
Interface implemented by *T | Yes | Yes |
Method call convenience vs interface implementation
| Situation | Allowed? | Why |
|---|---|---|
value.PointerMethod() on an addressable value | Often yes | Go can implicitly take the address for the call |
Assigning value to an interface requiring that pointer method | No | Interface checks the method set of T, not call convenience |
Assigning &value to that interface | Yes | *T contains pointer receiver methods |
When to choose each
| Use case | Better choice |
|---|---|
| Method should not modify receiver and type is small | Value receiver |
| Method must modify receiver | Pointer receiver |
| Type contains mutexes or large fields | Pointer receiver |
Interface should be implementable by both T and *T | Value receiver, if appropriate |
Cheat Sheet
Quick rules
- A type implements an interface if its method set contains all required methods.
Thas methods with receiverT.*Thas methods with receiverTand*T.- If an interface requires a method defined on
*T, then only*Timplements that interface.
Fast diagnosis
If you see:
X does not implement Y (... method has a pointer receiver)
check these:
- Is the missing method declared as
func (x *X) Method()? - Are you passing
Xwhere*Xis needed? - Are you assigning a value instead of a pointer to an interface?
- Is your constructor returning
Xwhen it should return*X? - Are mixed receiver types causing confusion?
Common fix
Change this:
var y Y = x
into this:
var y Y = &x
or create the value as a pointer from the start:
x := &X{}
Compile-time interface check
var _ Y = (*X)(nil)
Important edge case
This may compile:
x.Method()
but this may still fail:
var y Y = x
because direct method calls and interface implementation follow related but different rules.
FAQ
Why does x.SomeMethod() work, but x still does not implement the interface?
Because Go may automatically take the address for a direct method call on an addressable value. Interface implementation does not use that convenience; it checks the actual method set of the type.
Does *T get methods defined on T?
Yes. The method set of *T includes methods with receiver T and methods with receiver *T.
Does T get methods defined on *T?
No. A value type does not get pointer receiver methods in its method set.
Should I always use pointer receivers in Go?
No. Use pointer receivers when you need to modify the receiver, avoid copying large structs, or keep method behavior consistent. Small immutable-like types can use value receivers.
How do I know whether to pass x or &x to an interface?
Check how the required methods are declared. If any required method uses a pointer receiver on your concrete type, pass &x.
Can a type with mixed receivers still be valid?
Yes. It is valid Go, but it can be confusing. Be consistent unless you have a clear reason.
Is this about interfaces only?
No. Pointer receivers affect method sets in general, but the confusion most often appears when assigning concrete types to interfaces.
Mini Project
Description
Build a small logging system with an interface and a concrete logger type. This project demonstrates how pointer receiver methods affect whether a struct can be assigned to an interface. It also shows a realistic reason for pointer receivers: updating internal state such as counters or stored messages.
Goal
Create a logger that satisfies an interface only when used as a pointer, then use it to record messages and count how many were written.
Requirements
- Define a
Loggerinterface with aLog(string)method and aCount() intmethod. - Create a
MemoryLoggerstruct that stores log messages. - Implement
Log(string)so it adds a message to the struct. - Implement
Count() intso it returns the number of stored messages. - Use the logger through the interface and print the final count.
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.