LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2618 min read

Methods

Learn how to attach methods to Go types with receivers, and understand when to use value receivers versus pointer receivers.

Introduction

Go doesn't have classes, but it does let you attach functions directly to a type — these are called methods. A method is just a function with an extra parameter, called a receiver, that specifies which type it belongs to. Understanding the choice between value and pointer receivers is one of the more important decisions you'll make repeatedly in Go code.

What You Will Learn
  • How to define a method using a receiver.
  • What a value receiver is and when it's appropriate.
  • What a pointer receiver is and when it's necessary.
  • How Go automatically converts between values and pointers when calling methods.
  • That methods can be defined on non-struct named types too.

Defining a Method

A method is declared like a regular function, but with a receiver argument in parentheses between the func keyword and the method name. The receiver names the type the method belongs to.

package main
import "fmt"
type Rectangle struct {
Width, Height float64
}
// Area is a method on Rectangle, with receiver "r".
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func main() {
rect := Rectangle{Width: 5, Height: 3}
fmt.Println("Area:", rect.Area())
}
Output

Click Run to see what this code prints.

Value Receivers

A value receiver, written (r Rectangle), receives a copy of the struct it's called on. Changes made to the receiver inside the method have no effect on the original value — exactly like a regular value parameter.

package main
import "fmt"
type Rectangle struct {
Width, Height float64
}
// Value receiver: operates on a copy, cannot mutate the original.
func (r Rectangle) Scale(factor float64) Rectangle {
r.Width *= factor
r.Height *= factor
return r // return a new, scaled copy
}
func main() {
rect := Rectangle{Width: 4, Height: 2}
scaled := rect.Scale(2)
fmt.Println("original:", rect)
fmt.Println("scaled: ", scaled)
}
Output

Click Run to see what this code prints.

Pointer Receivers

A pointer receiver, written (r *Rectangle), receives a pointer to the original struct. Changes made through the receiver inside the method directly mutate the caller's original value — this is the only way for a method to modify its receiver.

package main
import "fmt"
type Rectangle struct {
Width, Height float64
}
// Pointer receiver: mutates the original struct directly.
func (r *Rectangle) ScaleInPlace(factor float64) {
r.Width *= factor
r.Height *= factor
}
func main() {
rect := Rectangle{Width: 4, Height: 2}
rect.ScaleInPlace(3)
fmt.Println("mutated in place:", rect)
}
Output

Click Run to see what this code prints.

When to Use Each

SituationUse
Method needs to modify the receiverPointer receiver
Receiver is a large struct (copying is expensive)Pointer receiver
Receiver is small and immutable by convention (e.g. a simple value type)Value receiver
Consistency: some methods on the type already use pointer receiversPointer receiver, for consistency
The type contains a mutex or other field that must not be copiedPointer receiver

A widely followed rule of thumb: if any method on a type needs a pointer receiver, use pointer receivers for all methods on that type, even the ones that don't strictly need to mutate anything. This keeps the type's method set consistent and avoids subtle bugs.

Go Handles the Conversion for You

You might expect to always need &rect to call a pointer-receiver method, or *ptr to call a value-receiver one. Go is more convenient than that: if you have an addressable value, Go automatically takes its address for you when calling a pointer-receiver method, and automatically dereferences a pointer when calling a value-receiver method.

package main
import "fmt"
type Counter struct {
value int
}
func (c *Counter) Increment() {
c.value++
}
func (c Counter) Value() int {
return c.value
}
func main() {
c := Counter{} // a plain value, not a pointer
c.Increment() // Go automatically does (&c).Increment()
c.Increment()
fmt.Println(c.Value()) // 2
}
Output

Click Run to see what this code prints.

Methods on Non-Struct Types

Methods aren't limited to structs — you can define a method on any named type declared in your own package, including one based on a built-in type like int or string.

package main
import "fmt"
type Celsius float64
func (c Celsius) ToFahrenheit() float64 {
return float64(c)*9/5 + 32
}
func main() {
temp := Celsius(100)
fmt.Printf("%.1f°C is %.1f°F\n", float64(temp), temp.ToFahrenheit())
}
Output

Click Run to see what this code prints.

Common Mistakes

Avoid These Mistakes
  • Using a value receiver on a method that needs to mutate the struct, then being confused why the change doesn't stick.
  • Mixing value and pointer receivers inconsistently across methods on the same type, which can lead to surprising method-set behavior.
  • Trying to define a method on a type declared in a different package — Go only allows methods on types defined in the same package as the method.
  • Forgetting that a large struct passed with a value receiver is copied on every method call, hurting performance.
  • Assuming methods work on unnamed types like map[string]int directly — you must define a named type first.

Best Practices

  • Use a pointer receiver whenever the method needs to mutate the receiver.
  • Once one method on a type uses a pointer receiver, make all of that type's methods use pointer receivers for consistency.
  • Use value receivers for small, simple, immutable-by-convention types where copying is cheap.
  • Name receivers with a short, consistent abbreviation of the type (e.g. r for Rectangle), not "this" or "self".
  • Define methods on named types to add behavior to simple values, like Celsius or a custom Duration type.

Frequently Asked Questions

It's the special parameter before the method name that ties the function to a specific type, e.g. (r Rectangle) or (r *Rectangle). It's how Go associates a method with the type it operates on.

No — values that aren't addressable (such as a struct stored directly as a map value) cannot have pointer-receiver methods called on them directly. You'd need to copy it out into a variable first.

Under the hood, yes — a method is essentially a function with an implicit receiver parameter. The syntax rect.Area() is equivalent to something like Area(rect) conceptually, but methods enable interface satisfaction, which plain functions cannot do.

Key Takeaways

  • A method is a function with a receiver that ties it to a specific type.
  • A value receiver operates on a copy; changes do not affect the original.
  • A pointer receiver operates on the original; changes made through it persist.
  • If any method needs a pointer receiver, use pointer receivers consistently across that type.
  • Go automatically converts between addressable values and pointers when calling methods, so you rarely need to write & or * explicitly at the call site.

Summary

Methods let you attach behavior to your own types without needing classes or inheritance, and the value-versus-pointer receiver decision is one you'll make on nearly every type you define. Next, you'll learn about interfaces — how Go achieves polymorphism through implicit, structural typing rather than explicit "implements" declarations.

Next Lesson →

Interfaces