Methods & Receivers

intermediate go methods receivers

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.

value receiver (r Rect)
gets a COPY
mutations → lost
good for: read-only,
small structs
pointer receiver (r *Rect)
gets the ADDRESS
mutations → stick
good for: mutation,
big structs
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:

  1. If the method needs to modify the receiver, use a pointer receiver.
  2. 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.