Value receiver vs. pointer receiver: a duel that never ends
\n\nIf 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\nSpoiler: 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\nIn 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\nWhat is a receiver in Go?
\n\nFor 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\ntype 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\nIn 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\nWhen to use value receiver
\n\nLet's start with the simplest and safest option.
\n\n1. The method does not modify the receiver
\n\nThis 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\ntype Rectangle struct {\n Width, Height float64\n}\n\nfunc (r Rectangle) Area() float64 {\n return r.Width * r.Height\n}\n\n\nNo one expects that calculating the area will change the rectangle itself. Value receiver is the ideal choice here.
\n\n2. Small structs
\n\nIf 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\n3. Safety and immutability
\n\nSometimes 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\ntype Config struct {\n Timeout int\n}\n\nfunc (c Config) WithTimeout(t int) Config {\n c.Timeout = t\n return c\n}\n\n\nThis is a functional programming pattern: the method returns a new copy without touching the original. Perfect for immutable configs.
\n\nWhen to choose pointer receiver
\n\nNow here's where it gets interesting. Pointer receiver is a powerful tool, but you need to be careful with it.
\n\n1. The method modifies the receiver
\n\nThis is the most obvious case. If you're writing SetName, UpdateStatus, or Increment — you need a pointer.
type Counter struct {\n Value int\n}\n\nfunc (c *Counter) Increment() {\n c.Value++\n}\n\n\nWithout a pointer, you'll be incrementing a copy, and the original will remain unchanged. A classic beginner's mistake.
\n\n2. Large structs
\n\nIf 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\ntype 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\nHere, pointer receiver is not just a convenience, but a performance optimization.
\n\n