Saturday, August 15, 2026
Go Pointer vs Value Receiver: Which Should You Use?
Posted by

Go Pointer vs Value Receiver: Which Should You Use?
Use a pointer receiver when the method must change the value, when the type contains a sync.Mutex (or anything that must not be copied), or when the struct is large enough that copying it on every call is wasteful. Use a value receiver for small, copy-cheap types that you treat as data (time.Time, a 2D point). Once any method on the type needs a pointer, give all methods pointer receivers.
The Go Tour names two reasons for a pointer: mutation, and avoiding a copy of a large value. Everything else is a consequence of those two plus method sets.
The failure
A value receiver looks like a setter. It is not.
package main
import "fmt"
type Counter struct {
N int
}
func (c Counter) Inc() { c.N++ }
func main() {
c := Counter{N: 0}
c.Inc()
fmt.Println(c.N) // 0
}
The compiler is happy. Inc incremented a copy and threw it away.
The other silent failure: mixing receivers, then assigning a value to an interface.
type Setter interface {
Set(int)
}
func (c *Counter) Set(v int) { c.N = v }
func main() {
var s Setter
c := Counter{}
// s = c // compile error: Counter does not implement Setter
s = &c // ok
}
T's method set is value-receiver methods only. *T's method set is both. A pointer-receiver method is not on the value.
Why this happens
A method is a function whose first argument is the receiver. func (c Counter) Inc() is Inc(c Counter). Go copies c. func (c *Counter) Inc() copies the pointer, which still names the same Counter.
Go will insert & or * for you when the value is addressable: c.Inc() on a variable becomes (&c).Inc(). It will not take the address of a map index, a function return, or any other temporary. That is why m["x"].Inc() fails if Inc has a pointer receiver, and why makeCounter().Inc() fails the same way.
Copying a struct that holds a sync.Mutex copies the mutex. Each call then locks a different mutex. go vet flags this. Pointer receivers avoid the copy.
Pointer receivers can force the value onto the heap (escape analysis). For a tiny struct, a value receiver is often cheaper. Do not pick the receiver for speed until a benchmark says it matters.
The decision
Walk the list in order. Stop at the first yes.
- Does the method mutate the receiver, reassign a slice header, or append in a way that must stick? → pointer.
- Does the type contain a mutex or other non-copyable field? → pointer on every method.
- Is the struct large (imagine passing every field as a separate argument)? → pointer.
- Does any other method on this type already use a pointer? → pointer, for a uniform method set.
- Is the type a small immutable value? → value.
- Otherwise default to pointer. It is the choice that does not silently no-op a later setter.
func (c *Counter) Inc() { c.N++ }
func (c *Counter) Value() int { return c.N } // pointer too, for consistency
For a value object, keep it a value:
type Point struct{ X, Y float64 }
func (p Point) Translate(dx, dy float64) Point {
return Point{X: p.X + dx, Y: p.Y + dy}
}