A method is just a function with a special receiver argument that attaches it to a type. Think of it like “this function belongs to this type.”
type Rect struct {
W, H int
}
func (r Rect) Area() int { // (r Rect) is the receiver
return r.W * r.H
}
rect := Rect{W: 3, H: 4}
fmt.Println(rect.Area()) // 12
That (r Rect) bit between func and the name is the receiver. It’s what makes Area a method on Rect instead of a plain function.
The big one: value vs pointer receiver
This is a top interview question. There are two flavors of receiver:
- Value receiver
(r Rect)— the method gets a copy of the value. Changes inside don’t affect the original. - Pointer receiver
(r *Rect)— the method gets a pointer to the original. Changes inside DO affect the original.
In simple language: value receiver = work on a photocopy, pointer receiver = work on the real thing.
func (r Rect) ScaleCopy() { // value receiver
r.W *= 2 // mutates the copy only
}
func (r *Rect) Scale() { // pointer receiver
r.W *= 2 // mutates the original
}
box := Rect{W: 3, H: 4}
box.ScaleCopy()
fmt.Println(box.W) // 3 — unchanged, we scaled a copy
box.Scale()
fmt.Println(box.W) // 6 — changed, we scaled the real thing
When to use which
Two simple rules cover almost everything:
- If the method needs to modify the receiver, use a pointer receiver.
- If the struct is large (copying is expensive), use a pointer receiver to avoid the copy.
And one consistency rule: if any method on a type uses a pointer receiver, make them all pointer receivers. Mixing them is confusing and can cause subtle interface issues.
Small, read-only, immutable-feeling types (like a Point or a time.Time) are fine with value receivers.
The addressability rule
Here’s the gotcha. To call a pointer-receiver method, Go needs to take the address of the value. That only works if the value is addressable — basically, it has to live somewhere we can point at (a variable, a slice element, a struct field).
type Counter struct{ n int }
func (c *Counter) Inc() { c.n++ }
c := Counter{}
c.Inc() // OK — c is a variable, Go auto-takes &c
fmt.Println(c.n) // 1
// Counter{}.Inc() // COMPILE ERROR — a literal isn't addressable
Go is kind enough to auto-insert the & for us when calling c.Inc() on a variable. But a temporary value like Counter{} or a map element has no fixed address, so the pointer-receiver call won’t compile.
Interview soundbite
A value receiver works on a copy so mutations are lost; a pointer receiver works on the original so mutations stick and we avoid copying big structs. The rule of thumb: use a pointer receiver if we mutate or the struct is large, and keep receivers consistent across a type. Pointer-receiver methods need an addressable value — Go auto-takes the address of a variable, but a non-addressable literal won’t compile.