Is append in Go efficient?

I gotta admit, Go has been forcing me to pay a lot more attention to memory management and garbage collection than I ever cared to. And when I was appending to a slice, I couldn't help but wonder if that was the most efficient method to grow a slice. And since everything in Go is passed by value, I thought each slice had to be copied on to another bigger slice, append the element, and then return the bigger slice. Now that would obviously mean that the previous (smaller) slice would have to be garbage collected, which translates into performance taking a hit. What was the whole point of learning a statically typed language when Python was good enough, right?

Well, although Go does append values to a slice by creating a new slice, there's more to it than that. This blog post does a very good job of explaining it. To put it succinctly, slices with fewer than 1000 elements are doubled every time a new element needs to be appended, and there's not enough space. But after the first 1000 elements, it grows at the rate of 25% when there's not enough capacity. This is to reasonably ensure that the slice does not occupy more memory than it needs to.

The following code displays the memory address of the slice as it grows (The link would redirect to the Golang playground where you can test the code).

package main

import "fmt"

func main() {  
    var a []int
    for i := 0; i < 5; i++ {
        fmt.Printf("%p\n", &a)
        a = append(a, i)
    }
    fmt.Println(a)
}

The output is

0x104382e0  
0x104382e0  
0x104382e0  
0x104382e0  
0x104382e0  
[0 1 2 3 4]

The memory address is the same! It means that the new value is appended to the slice in the same memory space. And this means that the garbage collector wouldn't need to sweep away unnecessary temporary variables. So the append method might just be almost as efficient as when memory would be manually managed. Ingenious, I must admit.