Methods
Methods are functions with a receiver — a special parameter before the function name that binds the function to a type.
Introduction
Methods are functions with a receiver — a special parameter before the function name that binds the function to a type. Receivers can be value or pointer; pointer receivers are required when methods mutate state or when the struct is large.
Go does not have classes — methods can be defined on any type in the same package, including non-struct types like custom int aliases. Method sets determine which methods a type satisfies for interfaces — pointer vs value receiver affects interface satisfaction.
This lesson covers receiver choice, method expressions, and the idiomatic patterns used in the standard library (http.Server methods, bytes.Buffer Write).
The story
A Cloudflare rate-limiter attaches behavior to a Limiter struct: func (l *Limiter) Allow(key string) bool. Pointer receivers mutate internal token buckets; value receivers suit immutable value types. Kubernetes' client-go defines methods on typed clients — the pattern you'll implement daily in backend services.
Choosing pointer vs value receiver consistently across a type prevents subtle bugs when interface satisfaction and mutation expectations diverge.
Understanding the topic
Key concepts
- func (r ReceiverType) MethodName(params) return — value receiver.
- func (r *ReceiverType) MethodName — pointer receiver for mutation.
- Method set: value type has value methods; pointer type has both value and pointer methods.
- Methods on non-struct types: type MyInt int; func (m MyInt) Double() int.
- No method overloading — one method name per receiver type.
- Interface satisfaction uses method set of the type used.
Step-by-step explanation
- Compiler desugars method call: v.Method(x) → Method(v, x).
- Value receiver copies struct — mutations don't persist.
- Pointer receiver shares instance — mutations visible.
- Go auto-dereferences: ptr.Method() works for pointer receiver.
- Method expression: f := Type.Method; f(instance, args).
- Consistency: if any method needs pointer receiver, use pointer for all.
Practical code example
Bank account with pointer receiver for mutation and value receiver for read:
package mainimport "fmt"type Account struct {owner stringbalance float64}func NewAccount(owner string) *Account {return &Account{owner: owner}}func (a *Account) Deposit(amount float64) {if amount > 0 {a.balance += amount}}func (a *Account) Withdraw(amount float64) error {if amount > a.balance {return fmt.Errorf("insufficient funds")}a.balance -= amountreturn nil}func (a Account) Owner() string {return a.owner}func main() {acc := NewAccount("Alice")acc.Deposit(500)_ = acc.Withdraw(100)fmt.Printf("%s balance: %.2f\n", acc.Owner(), acc.balance)}
Line-by-line code explanation
func (l *Limiter) Allow(key string) booldeclares a method with a pointer receiver.l.tokens--mutates receiver state — only possible with pointer receivers.func (p Point) Distance() float64uses a value receiver for small immutable types.(&l).Allow(key)Go auto-takes address when calling pointer methods on values.method setsmust be consistent — don't mix pointer and value receivers on the same type.type Handler interface { ServeHTTP(...) }interfaces are satisfied implicitly by implementing methods.func NewLimiter(r rate) *Limiterconstructor returns a pointer ready for method calls.method expressions: Limiter.Allowextracts a method as a function value.
Key takeaway: Use pointer receiver when mutating or struct is large. Be consistent within a type. Constructor returns *Account for method calls.
Real-world use
Where you'll use this in production
- Domain model behavior: order.Place(), account.Withdraw().
- HTTP handlers as methods on controller struct with dependencies.
- Custom types with validation: Email.Validate().
- Builder pattern: cfg.WithTimeout(d).WithRetries(n).
Best practices
- Use pointer receiver for mutation and large structs.
- Be consistent — all methods on a type use same receiver kind.
- Keep methods on domain types; free functions for stateless ops.
- Avoid getter/setter boilerplate — export fields when appropriate.
- Document exported methods with godoc.
Common mistakes
- Value receiver on mutating method — changes lost.
- Mixing value and pointer receivers — confusing interface satisfaction.
- Nil pointer receiver call — panics if method doesn't nil-check.
- Too many methods on one type — split responsibilities.
- Method on interface type — not allowed, only on concrete/named types.
Advanced interview questions
Interview Prep
Practice concise answers, then expand each card for the explanation.
1BeginnerQuestionValue vs pointer receiver?+
Answer
2BeginnerQuestionCan methods be on any type?+
Answer
3IntermediateQuestionMethod set and interfaces?+
Answer
4IntermediateQuestionWhen nil pointer receiver is OK?+
Answer
5AdvancedQuestionDesign FileStore with Write, Read, Close methods.+
Answer
Summary
Methods attach behavior to types via value or pointer receivers. Pointer receivers for mutation; be consistent across methods. Method set determines interface satisfaction. Constructors return pointers for method call convenience. Next lesson: interfaces — Go's polymorphism mechanism.