append, len and cap
append adds elements and returns the new slice; when capacity runs out it allocates a bigger array and stops sharing memory.
s = append(s, v1, v2) // or s = append(s, other...)append is a built-in function that adds elements to the end of a slice. It does not change the slice you give it. It returns a new slice, which is why you almost always write s = append(s, v).
How it decides what to do
A slice has a length and a capacity. Capacity is the room available in the array underneath, counting from this slice's first element. If the new elements fit inside the capacity, append writes them into that existing array and returns a slice with a longer length. The old slice and the new one then share memory.
If they do not fit, append allocates a bigger array, copies the existing elements into it, writes the new ones, and returns a slice over that new array. The old slice is untouched and now shares nothing. So append may or may not share memory with its input, and you cannot tell by reading the call.
Growth is not promised
How much extra room append takes is decided by the runtime and is not part of the language. It has changed between Go releases, so never write code that depends on a particular capacity. Check with cap if you are curious; do not assert on it.
Shorthand and longhand
append(a, b...) appends every element of b. Without the three dots it would try to append b itself as one element and fail to compile. append(s, 1, 2) is the same as two separate appends.
What trips people up
Ignoring the return value. append(s, 1) on its own is a compile error, but t := append(s, 1) silently leaves s at its old length.
Example
package main
import "fmt"
func main() {
// Length 0, room for 2.
s := make([]int, 0, 2)
fmt.Println(len(s), cap(s))
s = append(s, 1)
s = append(s, 2)
fmt.Println(s, len(s), cap(s))
// No room left, so a new, larger array is allocated and copied into.
grown := append(s, 3)
// Because the array is new, this write cannot reach s.
grown[0] = 99
fmt.Println(s, grown)
fmt.Println(cap(grown) > cap(s))
// Three dots spread another slice's elements into the call.
more := append([]int{0}, s...)
fmt.Println(more)
}
// Output:
// 0 2
// [1 2] 2 2
// [1 2] [99 2 3]
// true
// [0 1 2]
When to use it
- Collect query results one row at a time when the count is unknown.
- Build an HTTP response body in a byte slice before writing it out.
- Filter a list down in place to avoid a second allocation.
- Concatenate two lists into a third without a manual copy loop.
More examples
Filtering without allocating
The standard in-place filter. It is safe because the write position never gets ahead of the read position. Reach for it in a hot path where the input is no longer needed afterwards, and never when the caller still wants the original.
package main
import "fmt"
func main() {
nums := []int{1, -2, 3, -4, 5}
// nums[:0] is a slice of length 0 over the same array, so every
// append below writes back into nums instead of allocating.
keep := nums[:0]
for _, n := range nums {
if n > 0 {
keep = append(keep, n)
}
}
fmt.Println(keep)
fmt.Println(nums) // the original order was overwritten in place
}
// Output:
// [1 3 5]
// [1 3 5 -4 5]
Joining slices, and spreading a string into bytes
Appending a string's bytes to a []byte is a special case the language allows, and it is how most request and response building is done without creating many intermediate strings.
package main
import "fmt"
func main() {
a := []int{1, 2}
b := []int{3, 4}
// Three dots spread b's elements. Without them this would not compile.
joined := append(a, b...)
fmt.Println(joined)
// A byte slice is a slice like any other, and a string may be
// spread into one directly.
line := []byte("GET ")
line = append(line, "/health"...)
fmt.Println(string(line))
}
// Output:
// [1 2 3 4]
// GET /health
Watching capacity grow
Shows that appending one at a time reallocates several times, and shows why a test must never assert an exact capacity. If those reallocations matter, pass the final size to make up front.
package main
import "fmt"
func main() {
var s []int
last := cap(s)
grows := 0
for i := 0; i < 100; i++ {
s = append(s, i)
if cap(s) != last {
// A bigger array was allocated and the elements copied over.
grows++
last = cap(s)
}
}
// The exact numbers are the runtime's business, so only check the shape.
fmt.Println(len(s), grows > 0, cap(s) >= len(s))
}
// Output:
// 100 true true
Discussion