ProgrammingBackend Developer

What are the differences between passing by reference and by value in Go? Why is this critical for structs and slices?

Pass interviews with Hintsage AI assistant

Answer

In Go, by default, all function arguments are passed by value: the value of the variable is copied. However, some types (for example, slices, maps, channels) are "wrappers" around internal structures (pointers). Passing a slice by value only copies the slice descriptor, not the data; both variables refer to the same array. In the case of structs — the entire structure is copied.

If you want to avoid copying and work with the original structure, you use pointer passing (*Struct).

Example:

type User struct { Name string Age int } func updateUser(u User) { u.Age = 30 // will only change the copy } func updateUserPtr(u *User) { u.Age = 30 // will change the original } func main() { u := User{"Ivan", 25} updateUser(u) fmt.Println(u.Age) // 25 updateUserPtr(&u) fmt.Println(u.Age) // 30 }

Trick Question

Are changes to a slice passed into a function always visible outside the function?

No!

  • If the contents of the slice are modified (slice[i] = ...), it is visible outside.
  • If the slice itself is modified (for example, slice = append(slice, ...)), and the result is not returned from the function — the new elements will be in a local copy, and you will lose them.

Example:

func addElem(s []int) { s = append(s, 100) } func main() { arr := []int{1,2,3} addElem(arr) fmt.Println(arr) // [1 2 3] — 100 was not added }

Examples of Real Errors Due to Ignorance of Nuances


Story

In one project, data structures with large struct fields (200+ bytes) were passed by value through channels between goroutines, causing huge overheads in copying and performance loss. After switching to pointer passing, latency decreased significantly.


Story

In an audit logging service, a developer passed a map between functions without explicit cloning. Changes made by one function unexpectedly altered data in another part of the program, causing confusion in the log.


Story

In a function for dynamically increasing a slice, the new slice was forgotten to be returned. As a result, changes were not reflected in the calling code, leading to the loss of some transactions. It was decided to return the new slice from the function.