Pointers vs. Values in Go: The Line Between Performance and Safety
\n\nImagine this: you're writing in Go, everything is going smoothly, but suddenly you notice the application starts to "lag" on large volumes of data. You open the profiler — and see that allocs/op (allocations per operation) are off the charts. Sound familiar? Most likely, the problem is in how you pass data to functions and return it. The question "Pointers vs. values in parameters and return values" is one of the hottest on Stack Overflow (thousands of views!), and today we'll break it down in a lively way with real examples.
\n\nGo is a language that loves simplicity. But this simplicity is deceptive: the choice between passing a value and a pointer can dramatically change your program's speed and memory consumption. Let's figure out when to use what, so your code flies instead of crawls.
\n\nWhy is this topic so important?
\n\nUnlike Java or Python, where everything is a reference to an object, in Go you explicitly choose: pass a copy of the structure or its address. It might seem like a small detail. But in reality, this fundamental decision affects:
\n\n- \n
- Execution speed — copying large structures takes time. \n
- Memory consumption — extra copies = extra allocations. \n
- Data safety — pointers allow mutating the original, values do not. \n
- Working with interfaces — interfaces in Go are a tricky mechanism that works differently with pointers and values. \n
Let's get straight to the point and look at the code.
\n\nExample #1: Small Structure
\n\nLet's start simple. Suppose we have a Point structure with two coordinates:
type Point struct {\n X, Y float64\n}\n\n// Passing by value\nfunc MoveByValue(p Point, dx, dy float64) Point {\n p.X += dx\n p.Y += dy\n return p\n}\n\n// Passing by pointer\nfunc MoveByPointer(p *Point, dx, dy float64) {\n p.X += dx\n p.Y += dy\n}\n\nIn the first case, we create a copy of the structure, modify it, and return a new one. In the second, we change the original directly. Which option is faster? For such a tiny structure (16 bytes) — there's almost no difference. Copying 16 bytes is an atomic operation, and the GC (garbage collector) doesn't create extra load. Moreover, passing by value can even be faster because a pointer requires additional dereferencing.
\n\nConclusion: For small structures (up to 4-8 fields of basic types), feel free to use values. It's safer and has no overhead.
\n\nExample #2: Large Structure
\n\nNow let's imagine we're working with user data:
\n\ntype User struct {\n ID int64\n Name string\n Email string\n Avatar []byte // 1 MB image\n CreatedAt time.Time\n UpdatedAt time.Time\n}\n\nHere, Avatar is a slice, which in Go is a reference type (header: pointer to data, length, capacity). But the structure itself, when copied, will only copy the slice header (24 bytes), not the actual bytes. However, if the structure has many fields, copying 100+ bytes on every function call becomes noticeable.
Let's run a simple benchmark:
\n\nfunc BenchmarkPassByValue(b *testing.B) {\n u := User{ID: 1, Name: "John", Avatar: make([]byte, 1024*1024)}\n for i := 0; i < b.N; i++ {\n _ = processUserByValue(u)\n }\n}\n\nfunc BenchmarkPassByPointer(b *testing.B) {\n u := &User{ID: 1, Name: "John", Avatar: make([]byte, 1024*1024)}\n for i := 0; i < b.N; i++ {\n _ = processUserByPointer(u)\n }\n}\n\nResult (on real hardware):
\n- \n
- PassByValue: ~120 ns/op, 0 allocations \n
- PassBy