Value vs Pointer Receiver in Go: What to Choose?

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Value receiver vs. pointer receiver: a duel that never ends

\n\n

If you've ever written in Go, you've certainly encountered this question. You open Stack Overflow — and there are thousands of views, hundreds of comments, and every second beginner (and not only) is racking their brain: "What should I choose? Value receiver or pointer receiver?"

\n\n

Spoiler: there is no single correct answer for all cases. But there are clear rules that will help you avoid mistakes in 95% of cases. And today we'll break them down once and for all.

\n\n

In this article, I won't just translate dry documentation. We'll go through real cases, look at performance, mutability, interfaces, and even how the Go community views different approaches. Let's go!

\n\n

What is a receiver in Go?

\n\n

For those just getting familiar with the language: a receiver is a parameter that binds a function to a type. This is how methods are implemented in Go.

\n\n
type User struct {\n    Name string\n    Age  int\n}\n\n// Value receiver\nfunc (u User) Greet() string {\n    return "Hello, I am " + u.Name\n}\n\n// Pointer receiver\nfunc (u *User) Birthday() {\n    u.Age++\n}\n
\n\n

In the first case, we work with a copy of the struct. In the second, we work with a pointer to the original. It seems like a small detail. But from this small detail, entire architectural decisions grow.

\n\n

When to use value receiver

\n\n

Let's start with the simplest and safest option.

\n\n

1. The method does not modify the receiver

\n\n

This is the golden rule. If the method simply reads data and doesn't change anything — use a value receiver. It's safe, predictable, and clear.

\n\n
type Rectangle struct {\n    Width, Height float64\n}\n\nfunc (r Rectangle) Area() float64 {\n    return r.Width * r.Height\n}\n
\n\n

No one expects that calculating the area will change the rectangle itself. Value receiver is the ideal choice here.

\n\n

2. Small structs

\n\n

If your struct consists of a couple of fields (like Rectangle above), copying costs almost nothing. Moreover, it can be faster than working with a pointer because the data is on the stack, not the heap.

\n\n

3. Safety and immutability

\n\n

Sometimes you intentionally want to guarantee that the method won't change the original data. Value receiver gives you this guarantee at the compiler level.

\n\n
type Config struct {\n    Timeout int\n}\n\nfunc (c Config) WithTimeout(t int) Config {\n    c.Timeout = t\n    return c\n}\n
\n\n

This is a functional programming pattern: the method returns a new copy without touching the original. Perfect for immutable configs.

\n\n

When to choose pointer receiver

\n\n

Now here's where it gets interesting. Pointer receiver is a powerful tool, but you need to be careful with it.

\n\n

1. The method modifies the receiver

\n\n

This is the most obvious case. If you're writing SetName, UpdateStatus, or Increment — you need a pointer.

\n\n
type Counter struct {\n    Value int\n}\n\nfunc (c *Counter) Increment() {\n    c.Value++\n}\n
\n\n

Without a pointer, you'll be incrementing a copy, and the original will remain unchanged. A classic beginner's mistake.

\n\n

2. Large structs

\n\n

If your struct weighs a hundred bytes or more, copying it on every method call is expensive. Especially if the method is called in a loop.

\n\n
type LargeData struct {\n    Buffer [1024]byte\n    Meta   map[string]interface{}\n}\n\n// Copying this on every call is painful\nfunc (l *LargeData) Process() {\n    // work with the original\n}\n
\n\n

Here, pointer receiver is not just a convenience, but a performance optimization.

\n\n

3. When the struct contains a mutex or

Blogs

Book Recommendations