Question

Go Value Receiver vs Pointer Receiver: When to Use Each

gofunctionpointers

Question

In Go, when should I use a value receiver instead of always using a pointer receiver?

For reference, consider this type and its methods:

type T struct {
    a int
}

func (tv T) Mv(a int) int         { return 0 }  // value receiver
func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver

The Go documentation says that for basic types, slices, and small structs, a value receiver is often very cheap, so unless the method's semantics require a pointer, a value receiver can be efficient and clear.

That raises a few questions:

  • Is a value receiver actually cheaper than a pointer receiver, or just cheap enough that the difference often does not matter?
  • In benchmarks, pointer receivers can appear faster even for very small structs. For example, with a struct containing a single field, the pointer receiver may outperform the value receiver.
  • The documentation also says value receivers can be "clear." Is that mainly about readability and semantics rather than raw performance?
  • If consistency matters, why not always use pointer receivers?

I would like to understand real cases where a value receiver clearly makes more sense than a pointer receiver, and whether microbenchmarks might be missing other important trade-offs.

Short Answer

By the end of this page, you will understand the real difference between value receivers and pointer receivers in Go, why this choice is mostly about method semantics and method sets rather than tiny benchmark differences, and how to choose the right receiver type in real code.

Concept

In Go, a method receiver decides how a method gets access to a value.

A method can receive either:

  • a copy of the value using a value receiver
  • a pointer to the value using a pointer receiver

Example:

type Counter struct {
    n int
}

func (c Counter) Value() int {
    return c.n
}

func (c *Counter) Increment() {
    c.n++
}

Here is the key idea:

  • Value() gets its own copy of Counter
  • Increment() gets access to the original Counter through a pointer

Why this matters

The choice is not only about speed. It affects:

  • Whether the method can modify the original value
  • Whether copies are made
  • Which interfaces the type implements
  • How clearly the API communicates intent
  • Whether copying the value is safe or desirable

Value receiver meaning

A value receiver means:

  • the method works with a copy of the receiver
  • changes inside the method do not affect the original value
  • the method often expresses read-only or value-like behavior

Example:

type Point struct {
    X int
    Y int
}

func (p Point) DistanceFromOrigin() int {
    return p.X*p.X + p.Y*p.Y
}

This method only reads data. A value receiver is natural because the method conceptually does not need shared mutable access.

Pointer receiver meaning

A pointer receiver means:

  • the method can modify the original value
  • copying large structs can be avoided
  • the method is tied to the identity of a specific object in memory

Example:

func (p *Point) Move(dx, dy int) {
    p.X += dx
    p.Y += dy
}

This changes the original Point, so a pointer receiver is required.

Why docs say value receivers can be cheap

For small values, copying is often so inexpensive that it is not worth optimizing around. A tiny struct may cost very little to copy, and Go can optimize aggressively.

So the docs are not saying:

  • "value receivers are always faster"

They are saying:

  • "for small, simple values, copying is cheap enough that using a value receiver is often perfectly reasonable"

That is an important difference.

Why microbenchmarks can mislead

A benchmark that calls a tiny method billions of times may measure:

  • inlining behavior
  • register passing
  • compiler optimizations
  • escape analysis effects
  • benchmark structure more than real-world behavior

In actual programs, the bigger questions are usually:

  • Should this method mutate state?
  • Should this type behave like a value?
  • Will copying this type be confusing or unsafe?
  • Do I want both T and *T to satisfy an interface?

A practical rule

Use a value receiver when:

  • the method does not need to modify the receiver
  • the type is small and copyable
  • the type is naturally value-like, such as a coordinate, time-like value, or configuration snapshot

Use a pointer receiver when:

  • the method must modify the receiver
  • the struct is large
  • copying is undesirable
  • the type contains synchronization primitives like sync.Mutex
  • you want to avoid accidental copies
  • method-set/interface behavior requires it

Consistency matters too

In Go, once some methods on a type need pointer receivers, many developers use pointer receivers for most or all methods on that type for consistency. That is common and often reasonable.

But using pointer receivers everywhere is not automatically better. It can blur intent for truly value-like types where copying is harmless and semantically correct.

The best choice is usually based on meaning first, then performance if profiling shows it matters.

Mental Model

Think of a value receiver and a pointer receiver like two ways of handing someone a document.

  • Value receiver: you hand them a photocopy
  • Pointer receiver: you hand them the original document

If they write on the photocopy, your original stays unchanged. If they write on the original, everyone sees the change.

In Go:

  • a value receiver works on its own copy
  • a pointer receiver works on the original value through its address

This helps answer the main design question:

  • If the method should only inspect data, a copy may be fine.
  • If the method should update shared state, it needs the original.

For small structs, making a photocopy is cheap. For large structs or sensitive structs, you may not want copies at all.

Take Quiz

Syntax and Examples

In Go, both receiver styles look similar, but they behave differently.

Basic syntax

type User struct {
    Name string
    Age  int
}

// Value receiver
func (u User) IsAdult() bool {
    return u.Age >= 18
}

// Pointer receiver
func (u *User) HaveBirthday() {
    u.Age++
}

Example: value receiver does not change the original

package main

import "fmt"

type Counter struct {
    n int
}

func (c Counter) AddOneWrong() {
    c.n++
}

func main() {
    c := Counter{n: 5}
    c.AddOneWrong()
    fmt.Println(c.n) // 5
}

Why?

AddOneWrong() receives a copy of c, so incrementing c.n changes only the copy.

Example: pointer receiver changes the original

package main

import "fmt"

type Counter struct {
    n int
}

func (c *Counter) AddOne() {
    c.n++
}

func main() {
    c := Counter{n: 5}
    c.AddOne()
    fmt.Println(c.n) // 6
}

Go automatically takes the address in many cases when calling a pointer receiver on an addressable value like c.

Example: value receiver is good for read-only behavior

package main

import "fmt"

type Rectangle struct {
    Width  int
    Height int
}

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

func main() {
    rect := Rectangle{Width: 4, Height: 3}
    fmt.Println(rect.Area()) // 12
}

Area() does not need to mutate anything, so a value receiver is a clean choice.

Important note about reference-like fields

A value receiver copies the struct itself, but some fields may still refer to shared underlying data.

package main

import "fmt"

type Bag struct {
    Items []string
}

func (b Bag) ChangeFirstItem() {
    b.Items[0] = "changed"
}

func main() {
    bag := Bag{Items: []string{"apple", "banana"}}
    bag.ChangeFirstItem()
    fmt.Println(bag.Items) // [changed banana]
}

Even with a value receiver, the slice header is copied, but it still points to the same underlying array. So receiver style does not magically make nested data immutable.

Step by Step Execution

Consider this example:

package main

import "fmt"

type Score struct {
    points int
}

func (s Score) TryAdd() {
    s.points += 10
    fmt.Println("inside TryAdd:", s.points)
}

func (s *Score) Add() {
    s.points += 10
    fmt.Println("inside Add:", s.points)
}

func main() {
    score := Score{points: 20}

    score.TryAdd()
    fmt.Println("after TryAdd:", score.points)

    score.Add()
    fmt.Println("after Add:", score.points)
}

Execution trace

1. Create the value

score := Score{points: 20}

Now score.points is 20.

2. Call the value receiver method

score.TryAdd()

Because TryAdd has a value receiver, Go passes a copy of score into the method.

Inside the method:

s.points += 10

The copied value changes from 20 to 30.

So this line prints:

inside TryAdd: 30

But the original score in main is still unchanged.

3. Print after the value receiver call

fmt.Println("after TryAdd:", score.points)

This prints:

after TryAdd: 20

4. Call the pointer receiver method

score.Add()

Go sees that Add needs *Score, and score is addressable, so it effectively uses &score.

Inside the method:

s.points += 10

This changes the original score.points from 20 to 30.

So this prints:

inside Add: 30

5. Print after the pointer receiver call

fmt.Println("after Add:", score.points)

This prints:

after Add: 30

Final output

inside TryAdd: 30
after TryAdd: 20
inside Add: 30
after Add: 30

This is the core behavior difference between the two receiver types.

Real World Use Cases

When value receivers make sense

Small value objects

Types that are naturally treated as plain values often use value receivers:

  • coordinates
  • RGB colors
  • money amounts
  • timestamps or durations
  • simple geometry types

Example:

type Point struct {
    X, Y int
}

func (p Point) String() string {
    return fmt.Sprintf("(%d, %d)", p.X, p.Y)
}

This does not need shared mutable state.

Computed helper methods

Methods that only calculate or format something are often good candidates.

type FileInfo struct {
    Name string
    Size int64
}

func (f FileInfo) IsEmpty() bool {
    return f.Size == 0
}

Safer APIs for immutable-style behavior

A value receiver can communicate: "this method does not change the receiver."

That can make code easier to reason about.

When pointer receivers make sense

Mutating methods

If the method updates the struct, use a pointer receiver.

type Account struct {
    Balance int
}

func (a *Account) Deposit(amount int) {
    a.Balance += amount
}

Large structs

If a struct contains many fields, repeatedly copying it may be wasteful.

Types with internal state or identity

Examples:

  • caches
  • database connections
  • buffers
  • structs with counters or statistics
  • structs containing sync.Mutex

These should usually use pointer receivers.

Shared state in services

In web apps or APIs, service structs often hold dependencies and state:

type UserService struct {
    repo Repository
    log  Logger
}

func (s *UserService) CreateUser(name string) error {
    return s.repo.Save(name)
}

These are usually used through pointers.

Take Quiz

Real Codebase Usage

In real Go codebases, developers often choose receiver types based on a few practical patterns.

1. Read-only methods on small value types

For tiny domain types, value receivers are common.

type Temperature struct {
    Celsius float64
}

func (t Temperature) Fahrenheit() float64 {
    return t.Celsius*9/5 + 32
}

This type behaves like a number with helper methods.

2. Mutating methods use pointer receivers

This is one of the clearest patterns.

type Config struct {
    Debug bool
}

func (c *Config) EnableDebug() {
    c.Debug = true
}

3. If one method needs a pointer, many teams use pointers for all methods

This avoids a mixed method set unless there is a strong reason not to.

type Buffer struct {
    data []byte
}

func (b *Buffer) Write(p []byte) {
    b.data = append(b.data, p...)
}

func (b *Buffer) Len() int {
    return len(b.data)
}

Even though Len() does not mutate, it may still use a pointer receiver for consistency.

4. Interface implementation affects the decision

Method sets matter in Go.

If an interface requires a method with a receiver that you implemented on *T, then only *T implements that interface, not T.

type Stringer interface {
    String() string
}

type Person struct {
    Name string
}

func (p *Person) String() string {
    return p.Name
}

Now *Person implements Stringer, but Person does not.

That can be useful or annoying depending on your API design.

5. Avoid copying types that should never be copied

This is very important for structs containing:

  • sync.Mutex
  • sync.RWMutex
  • atomic state
  • internal handles

These should generally use pointer receivers and be passed around carefully.

6. Validation and guard-clause methods

Methods that validate state may use either style depending on the type.

For small value-like structs:

type Range struct {
    Min int
    Max int
}

func (r Range) Valid() bool {
    return r.Min <= r.Max
}

For service objects or larger entities, pointer receivers are more common.

7. Builders and stateful helpers

If a method chains updates, pointer receivers are usually the right fit.

type Query struct {
    limit int
}

func (q *Query) Limit(n int) *Query {
    q.limit = n
    return q
}

Common Mistakes

1. Using a value receiver when you expect mutation

Broken example:

type Counter struct {
    n int
}

func (c Counter) Increment() {
    c.n++
}

This does not change the original value.

Fix:

func (c *Counter) Increment() {
    c.n++
}

2. Assuming a value receiver makes everything immutable

Broken assumption:

type Data struct {
    Items []int
}

func (d Data) Change() {
    d.Items[0] = 99
}

Even though d is a copy, the slice inside it still points to shared backing data.

To avoid surprises:

  • remember that slices, maps, channels, and function values are reference-like
  • copy nested data explicitly when needed

3. Mixing receiver types carelessly

Go allows mixing value and pointer receivers, but it can make APIs confusing.

type User struct {
    Name string
}

func (u User) DisplayName() string { return u.Name }
func (u *User) Rename(name string) { u.Name = name }

This can be okay, but if overused it can make method sets and interface behavior harder to reason about.

A common guideline is:

  • if some methods must use pointer receivers, consider using pointer receivers consistently for the type

4. Forgetting method-set rules with interfaces

type Speaker interface {
    Speak()
}

type Dog struct{}

func (d *Dog) Speak() {}

This works:

var s Speaker = &Dog{}

This does not:

var s Speaker = Dog{}

Because Dog does not have the pointer-receiver method in its method set.

5. Copying structs that contain mutexes

Broken pattern:

type SafeCounter struct {
    mu sync.Mutex
    n  int
}

func (s SafeCounter) Increment() {
    s.mu.Lock()
    defer s.mu.Unlock()
    s.n++
}

This copies the mutex, which is a serious problem.

Fix:

func (s *SafeCounter) Increment() {
    s.mu.Lock()
    defer s.mu.Unlock()
    s.n++
}

6. Over-optimizing based on tiny benchmarks

A benchmark may show a nanosecond-level difference, but that does not always matter in application code.

Avoid this mistake by asking:

  • does the method need mutation?
  • is the type large?
  • does interface behavior matter?
  • has profiling shown a real bottleneck?

Comparisons

AspectValue ReceiverPointer Receiver
ReceivesA copy of the valueA pointer to the original value
Can modify original?NoYes
Copy costMay copy the structAvoids copying the struct itself
Good for small value-like types?YesSometimes, but not always needed
Good for large structs?Usually less idealUsually better
Interface method setMethod belongs to both T and *TMethod belongs only to *T
Works with mutex-containing structs?Dangerous if copiedPreferred
Expresses read-only intent clearly?Often yesLess strongly

Method set comparison

type T struct{}

func (t T) ValueMethod() {}
func (t *T) PointerMethod() {}

What methods does T have?

  • ValueMethod()

What methods does *T have?

  • ValueMethod()
  • PointerMethod()

That is why *T can do everything T can do, but T cannot always do everything *T can do.

Rule of thumb comparison

SituationBetter choice
Method changes receiver statePointer receiver
Method only reads small structValue receiver
Struct contains mutex or unsafe-to-copy statePointer receiver
Type should feel like a plain valueValue receiver
You want only pointer values to satisfy an interfacePointer receiver
You want both T and *T to satisfy an interfaceValue receiver
Take Quiz

Cheat Sheet

Quick rules

  • Use a pointer receiver if the method must modify the receiver.
  • Use a pointer receiver if the struct is large.
  • Use a pointer receiver if the struct contains sync.Mutex, sync.RWMutex, or similar state.
  • Use a value receiver for small, value-like, read-only types.
  • If you mix receiver types, do it intentionally.

Syntax

func (v T) Method() {}
func (p *T) Method() {}

Key behavior

type C struct{ n int }

func (c C) A()  { c.n++ }   // changes copy only
func (c *C) B() { c.n++ }   // changes original

Method set rule

If:

func (t T) V() {}
func (t *T) P() {}

Then:

  • T has method V
  • *T has methods V and P

Good value receiver examples

  • Point.Area()
  • Time.IsZero()
  • Color.String()
  • Range.Valid()

Good pointer receiver examples

  • Counter.Increment()
  • Buffer.Write()
  • Account.Deposit()
  • Cache.Set()

Important edge case

A value receiver copies the struct, not necessarily all underlying data.

If the struct contains:

  • slices
  • maps
  • pointers
  • channels

then the copy may still refer to shared data.

Performance tip

Do not choose receiver type based only on microbenchmarks. Start with correct semantics, then profile real code.

FAQ

When should I use a value receiver in Go?

Use a value receiver when the method does not need to modify the receiver and the type is small, simple, and naturally value-like.

Are pointer receivers always faster in Go?

No. They can avoid copying, but the real performance difference depends on the type, compiler optimizations, and usage pattern. Tiny benchmarks often do not reflect real application performance.

Can a value receiver method change slice or map contents?

Yes. The struct itself is copied, but slices and maps refer to underlying shared data. Changing that underlying data can still affect the original.

Why do pointer receivers affect interface implementation?

Because methods with receiver *T belong only to the method set of *T, not T. That means only pointer values implement an interface requiring those methods.

Should I use pointer receivers for all methods on a type?

Often yes if the type is mutable, large, or already has mutating methods. But for small value-like types, value receivers can better express intent.

Is mixing value and pointer receivers allowed?

Yes, but do it carefully. Mixing can make code harder to understand, especially when interfaces are involved.

Why is copying a struct with a mutex dangerous?

A mutex has internal state and should not be copied after use. Using value receivers on such structs can accidentally copy the mutex and lead to incorrect behavior.

What matters more: semantics or benchmark speed?

Usually semantics. Choose the receiver type that matches the meaning of the method and the safety of the type. Optimize later if profiling shows a real issue.

Take Quiz

Mini Project

Description

Build a small Go program that models a bank account and a transaction summary. This project demonstrates when value receivers are a good fit for read-only behavior and when pointer receivers are required for mutation.

Goal

Create a program where read-only methods use value receivers and state-changing methods use pointer receivers.

Requirements

  1. Create an Account struct with owner name and balance.
  2. Add a value receiver method that returns a formatted account summary.
  3. Add a pointer receiver method for depositing money.
  4. Add a pointer receiver method for withdrawing money if enough balance exists.
  5. Show in main that summary methods do not change state, but deposit and withdraw do.
Take Quiz

Keep learning

Related questions

View more