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 ofCounterIncrement()gets access to the originalCounterthrough 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
Tand*Tto 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.
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.
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.Mutexsync.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
| Aspect | Value Receiver | Pointer Receiver |
|---|---|---|
| Receives | A copy of the value | A pointer to the original value |
| Can modify original? | No | Yes |
| Copy cost | May copy the struct | Avoids copying the struct itself |
| Good for small value-like types? | Yes | Sometimes, but not always needed |
| Good for large structs? | Usually less ideal | Usually better |
| Interface method set | Method belongs to both T and *T | Method belongs only to *T |
| Works with mutex-containing structs? | Dangerous if copied | Preferred |
| Expresses read-only intent clearly? | Often yes | Less 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
| Situation | Better choice |
|---|---|
| Method changes receiver state | Pointer receiver |
| Method only reads small struct | Value receiver |
| Struct contains mutex or unsafe-to-copy state | Pointer receiver |
| Type should feel like a plain value | Value receiver |
| You want only pointer values to satisfy an interface | Pointer receiver |
You want both T and *T to satisfy an interface | Value receiver |
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:
Thas methodV*Thas methodsVandP
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.
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
- Create an
Accountstruct with owner name and balance. - Add a value receiver method that returns a formatted account summary.
- Add a pointer receiver method for depositing money.
- Add a pointer receiver method for withdrawing money if enough balance exists.
- Show in
mainthat summary methods do not change state, but deposit and withdraw do.
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.