Skip to content

Methods

A method is a function with a receiver — an extra parameter that appears between the func keyword and the function name. The receiver binds the function to a named type.

func (r Rectangle) Area() float64 {
return r.Width * r.Height
}

Here r Rectangle is the receiver. Area is now a method of Rectangle, callable as rect.Area().

The receiver can be either a value or a pointer:

func (c Counter) Value() int { return c.count } // value receiver
func (c *Counter) Inc() { c.count++ } // pointer receiver
Value receiver (t T)Pointer receiver (t *T)
SeesA copy of the valueThe original value
Can mutate?No — mutates the copy onlyYes
Callable onBoth T and *TBoth T (addressable) and *T
Use whenRead-only; small, cheap to copyMutation; large struct; consistent method set

Be consistent: if any method on a type needs a pointer receiver, give all methods of that type pointer receivers. Mixing causes subtle method-set issues when the type is used as an interface value.

Methods are not limited to structs. You can define methods on any named type in your package — except pointer types and interface types:

type Celsius float64
func (t Celsius) String() string {
return fmt.Sprintf("%.1f°C", float64(t))
}

You cannot define a method on a type from another package — for example, func (t time.Time) Weekday2() string { ... } fails with cannot define new methods on non-local type time.Time — but wrapping it in a local named type is always allowed.

The method set of a type determines which interfaces it satisfies:

  • The method set of T contains all methods with value receiver (t T).
  • The method set of *T contains all methods — both (t T) and (t *T).

This is why an interface requiring a pointer-receiver method can only be satisfied by *T, not by T.

Calling a pointer-receiver method on a value auto-takes its address — c.Inc() on a variable c really means (&c).Inc(). But Go can only take that address when c is addressable: a plain variable, a struct field of an addressable struct, or *p for some pointer p. Two everyday values are not addressable — a map value, and the result of a composite literal used directly.

type Counter struct{ count int }
func (c *Counter) Inc() { c.count++ }
func main() {
m := map[string]Counter{"a": {}}
m["a"].Inc() // map values are not addressable
}

Compiling this with Go 1.26.5 gives:

./main.go:15:9: cannot call pointer method Inc on Counter

The reason is structural: a map can rehash and move its entries at any time, so Go refuses to hand out a stable address into it. The fix is either to store pointers (map[string]*Counter) or to copy out, mutate, and store back.

The same error appears for a composite literal used directly, because a literal has no variable to take the address of:

Counter{}.Inc()
./main.go:12:12: cannot call pointer method Inc on Counter

Addressability also decides interface satisfaction. T’s method set only has value-receiver methods, so assigning a T value to an interface that needs a pointer-receiver method fails at compile time, not at the call:

type Incer interface{ Inc() }
var i Incer = Counter{}
./main.go:16:16: cannot use Counter{} (value of struct type Counter) as Incer value in variable declaration: Counter does not implement Incer (method Inc has pointer receiver)

var i Incer = &Counter{} compiles, because *Counter’s method set includes Inc.

A method value — c.Inc — is a function bound to the specific receiver c at the moment you take it. A method expression — Counter.Value — takes the type itself, and the receiver becomes the function’s first parameter:

c := &Counter{}
inc := c.Inc // bound to c already — inc() means c.Inc()
inc()
value := Counter.Value // unbound — receiver is the first argument
fmt.Println(value(*c))

Method values are handy for passing a bound method as a callback (http.HandleFunc("/x", h.serveX)); method expressions are rare, mostly seen in generic code that needs to call a method on a type parameter’s underlying type.

Calling a method through a nil pointer is legal in Go, as long as the method body never dereferences a nil field before checking for it. This is the standard way to write a recursive nil-safe method on a linked structure:

type List struct {
val int
next *List
}
func (l *List) Sum() int {
if l == nil {
return 0
}
return l.val + l.next.Sum()
}

var l *List; l.Sum() returns 0 without panicking — the method call itself does not dereference l, and the body’s own nil check handles it.

Idiomatic Go receivers are one or two letters derived from the type name — c Counter, r *Rectangle — never this or self. Keep the same receiver name for every method on a type; the standard library follows this throughout (func (b *Buffer) WriteString, func (t Time) Add).

Because any named type can have methods, a named function type can too. The standard library uses this to turn a plain function into something that satisfies an interface — http.HandlerFunc is a func(http.ResponseWriter, *http.Request) with a ServeHTTP method, which is the only thing http.Handler requires:

type IntOp func(int) int
func (f IntOp) Twice(x int) int {
return f(f(x))
}

IntOp(double).Twice(3) calls double on its own result — the function itself is the receiver.

sync.Mutex must never be copied after first use, because a copy has its own independent lock state. A value receiver copies the whole struct on every call — so a value-receiver method on a struct containing a mutex silently locks a copy, protecting nothing:

type Safe struct {
mu sync.Mutex
count int
}
func (s Safe) Inc() { // value receiver — copies mu on every call
s.mu.Lock()
defer s.mu.Unlock()
s.count++
}

go vet catches this exact bug:

main.go:11:9: Inc passes lock by value: command-line-arguments.Safe contains sync.Mutex

The fix is always a pointer receiver: func (s *Safe) Inc(). Any struct holding a sync.Mutex, sync.WaitGroup, or similar must use pointer receivers on every method, and should never be passed or returned by value.

package main
import "fmt"
type Counter struct {
count int
}
// Pointer receiver — mutates the struct
func (c *Counter) Inc() {
c.count++
}
// Value receiver — read-only
func (c Counter) Value() int {
return c.count
}
type Celsius float64
func (t Celsius) String() string {
return fmt.Sprintf("%.1f\u00b0C", float64(t))
}
// List demonstrates a nil pointer receiver: Sum handles a nil *List gracefully.
type List struct {
val int
next *List
}
func (l *List) Sum() int {
if l == nil {
return 0
}
return l.val + l.next.Sum()
}
// IntOp is a named function type with its own method.
type IntOp func(int) int
func (f IntOp) Twice(x int) int {
return f(f(x))
}
func main() {
c := &Counter{}
c.Inc()
c.Inc()
c.Inc()
fmt.Println(c.Value()) // 3
temp := Celsius(36.6)
fmt.Println(temp.String()) // 36.6°C
// Method value: bound to c the moment it is created.
inc := c.Inc
inc()
fmt.Println(c.Value()) // 4
// Method expression: receiver becomes an explicit first argument.
value := Counter.Value
fmt.Println(value(*c)) // 4
var empty *List
fmt.Println(empty.Sum()) // 0 — nil receiver, handled inside Sum
list := &List{val: 1, next: &List{val: 2}}
fmt.Println(list.Sum()) // 3
double := IntOp(func(x int) int { return x * 2 })
fmt.Println(double.Twice(3)) // 12
}
What you getBenefitCost
Value receiverCheap to reason about, callable on non-addressable valuesCopies the struct on every call
Pointer receiverCan mutate, no copyNeeds a nil-check, receiver must be addressable
Method on a non-struct typeExtends the behavior of any named type in your packageCannot add a method to a type from another package
Method valueBundles a receiver with a function, easy to pass as a callbackKeeps the receiver alive for the closure’s lifetime
Pointer receiver on a type holding a mutexThe only correct optionEvery method — even read-only ones — must take a pointer
  • Value and pointer receivers are interchangeable. A value receiver works on a copy — it cannot mutate the caller’s original value.
  • You can add a method to a type from another package. Go does not allow this; wrap the type in a local named type or use embedding instead.
  • T and *T have the same method set. *T’s method set is larger — it also includes the pointer-receiver methods, which affects interface satisfaction.
  • A map value with a pointer-receiver method just works, like a struct variable. It does not — map values are not addressable, so m[k].Inc() is a compile error, not a runtime surprise.
  • Copying a struct that embeds sync.Mutex is harmless if you don’t Lock right away. The copy is already broken the moment it exists; go vet flags the copy itself, not just the lock call.

💡 Real-world example

net/http: http.ResponseWriter is an interface — every Handler accepts it instead of a concrete type, which makes handlers easy to test with a mock. http.HandlerFunc is a named function type with a ServeHTTP method, so a plain function can satisfy http.Handler with a one-line conversion.

*bytes.Buffer and *http.Client both use a pointer receiver on every method because they hold internal state that methods mutate (buffered bytes, a connection pool) — consistency of receiver kind matters here.

When a generated snippet defines methods, check for these before trusting it:

  • Does every method on the type use the same receiver kind — all value, or all pointer? A mix is a warning sign.
  • If a method mutates a field, is the receiver a pointer? A value-receiver mutation compiles and silently does nothing.
  • Does the type’s struct contain a sync.Mutex, sync.WaitGroup, or similar? Every method must be a pointer receiver, and the type must never be copied or passed by value.
  • Is a pointer-receiver method ever called on a map value (m[k].Method()) or a fresh composite literal (T{}.Method())? Both fail to compile — addressability, not a runtime bug.
  • Does an interface variable get assigned a value type (var i Iface = T{}) when the required method has a pointer receiver? That’s a compile error the generated code may have never actually been run against.

🐛 Spot the bug

type Counter struct{ n int }
func (c *Counter) Inc() { c.n++ }
func main() {
m := map[string]Counter{"a": {}}
m["a"].Inc()
}
Show the answer

m["a"] is not addressable — a map value can be moved by a rehash at any time, so Go refuses to take its address for the pointer-receiver call. The real compiler message is cannot call pointer method Inc on Counter. Fix it by using map[string]*Counter, or by loading the value, mutating it, and storing it back: c := m["a"]; c.Inc(); m["a"] = c.

A method with a value receiver (c Counter) modifies c.count. What happens to the original variable?
Which type's method set includes both value-receiver and pointer-receiver methods?
Does this compile?
type Counter struct{ n int }
func (c *Counter) Inc() { c.n++ }

func main() {
    m := map[string]Counter{"a": {}}
    m["a"].Inc()
}
What does go vet report?
type Safe struct {
    mu    sync.Mutex
    count int
}
func (s Safe) Inc() {
    s.mu.Lock()
    defer s.mu.Unlock()
    s.count++
}
You want to define a method on type Kelvin float64. What is the constraint?
Which Counter does inc() increment?
c := &Counter{}
inc := c.Inc
c = &Counter{}
inc()