Go — how to choose between passing by value or reference Link to heading
One of the ways to make our program more efficient is to remove any unnecessary work. For example, when we pass a struct as an argument to…
Go — to pass by value or reference Link to heading
One of the ways to make our program more efficient is to remove any unnecessary work. For example, when we pass a struct as an argument to a function, we can choose to pass it by its value or by its reference. What is the difference and how does it affect the performance of our program?
Passing by value means that we create a copy of the struct and pass it to the function. This way, the function does not modify the original struct, but only works with the copy. For example:
type MyStruct struct {
foo int
bar float32
baz []uint8
}
func PassByValue(s MyStruct) {
// s is a copy of the struct
...
}
Passing by reference means that we pass a pointer to the struct, which allows the function to access and modify the original struct. For example:
func PassByRef(s *MyStruct) {
// s is a pointer to the struct
...
}
You might think that passing by reference is more efficient than passing by value, because it avoids making a copy of the struct. However, this is not always true. In fact, passing by value will typically run faster than passing by reference; let me provide a real-world example.
Passing by value will typically run faster than passing by reference
First, let’s analyze the costs of each method. When we pass by value, the only cost is copying the struct. This cost is minimal, unless the struct is very large in size. Moreover, the compiler may optimize this cost away if it decides to inline the function, which means that it replaces the function call with the function body. In this case, there is no cost at all.
When we pass by reference, there is a hidden cost that is much higher: dynamically allocating the struct on the heap and freeing it later during garbage collection. The heap is a memory area where objects are stored without a fixed order or size. Allocating and freeing objects on the heap is more expensive than on the stack, which is a memory area where objects are stored in a fixed order and size. The stack is used for passing by value.
So, when should we pass by reference? The rule of thumb is that we should only pass by reference if we need to modify the struct in place, such as in method functions that are attached to a type. However, as always with performance optimization, the best way is to benchmark and optimize empirically.
To illustrate this point, I will show you an example from a gunzip program implemented in Go. Gunzip is a program that decompresses files that are compressed using gzip format. In this program, there is a struct called CodeData that represents a code in gzip format:
type Code uint8
const (
LiteralCode = iota
EndOfBlock
Dictionary
)
type CodeData struct {
Tag Code
Value uint8
Distance uint16
Length uint16
}
In first version, there is an iterator that outputs CodeData by reference in the critical section of the program
func (c *CodeIterator) Next() (*CodeData, error) {
...
}
In the second version of the program, the iterator is modified to output CodeData by value:
func (c *CodeIterator) Next() (CodeData, error) {
...
}
Below are flamegraphs of these two versions. If you are interested in how to obtain the graphs, check out this article for Linux, this article for macOS, and this article for Go-specific.


The runtime reduced from 5.1s to 3.7s, which is more than 30% speed up just by changing from pass by reference to pass by value. As you can see from the flamegraphs, there is a large block of runtime.mallocgc in the first version (i.e., pass by reference) that is missing in the second version (pass by value). This means that passing by reference causes more memory allocation and garbage collection than passing by value.
For full code difference, feel free to check out the commit. You can easily reproduce the result as well.