Are Go slices passed to functions by reference or by value?

Answer: apparently both! There’s a clear underlying logic to this, but I still found it confusing at first.

If you have the following code fragment:


func main() {
  a := []string{"one"}
  test(a)
  fmt.Println(a)
}

func test(b []string) {
  b[0] = "one2"
}

You’ll end up with the result:

[one2]

So it appears that Go slices are passed by reference.

But then if you have this block of code:


func main() {
  a := []string{"one"}
  test(a)
  fmt.Println(a)
}

func test(b []string) {
  b[0] = "one2"
  b = append(b, "two")
}

You’ll again end up with the result:

[one2]

So existing elements of a slice can be modified by reference, but new members can’t be added.

This is because behind-the-scenes a slice is represented as a pointer to an array, with its length and capacity. The length and capacity are integers so any modifications to these in a function (e.g. by using append) are not reflected outside of the function.

If you do want to modify the slice from inside the function then you need code such as the following:


func main() {
  a := []string{"one"}
  test(a)
  fmt.Println(a)
}

func test(b *[]string) {
  (*b)[0] = "one2"
  *b = append(*b, "two")
}

You’ll end up with the result:

[one2 two]

Although Go’s approach is quite consistent in one way: everything is passed by value. It’s just that pointers are values that point to a data structure in memory, and the effect can be confusing. It makes it look as if Go is using a combination of passing by reference and by value.