Adding to the end
Go has no push method on slices. You add elements to the end with the built-in function append. It takes a slice and one or more values, and returns a slice with the values added:
arr1 := []int{1, 2, 3}
arr1 = append(arr1, 4)
fmt.Println(arr1)
[1 2 3 4]
Notice the assignment: arr1 = append(arr1, 4). The result must be stored. Calling append(arr1, 4) on its own line does not compile, and the compiler reports that the value of append is not used.
Why the result must be stored
A slice refers to an underlying array of a certain capacity. If there is spare room, append writes the new value there and returns a slice that is one element longer. If the array is full, append allocates a bigger array, copies the elements over and returns a slice that refers to the new array. Your old variable still describes the old slice, with its old length, so only the returned value is up to date.
That is also why appending to a nil slice works. A slice declared with var s []string starts empty, and appending to it builds it up:
var s []string
for i := 0; i < 3; i++ {
s = append(s, fmt.Sprint("item", i))
}
fmt.Println(s)
[item0 item1 item2]
Several values at once
append accepts any number of values, which are added in the order you list them. To add all the elements of another slice, follow it with ...:
arr1 = append(arr1, 5, 6)
more := []int{7, 8}
arr1 = append(arr1, more...)
fmt.Println(arr1, len(arr1))
[1 2 3 4 5 6 7 8] 8
The exercise
You start with these slices:
fruits := []string{"mango", "kiwi", "grapefruit"}
ages := []int{14, 32, 50, 21}
- Append
"apple"tofruits. It goes at the end, as the fourth fruit. - Append
10and then23toages, with two calls. The order matters, because each value lands after the previous one.
fruits = append(fruits, "apple")
ages = append(ages, 10)
ages = append(ages, 23)
Afterwards fruits is [mango kiwi grapefruit apple] and ages is [14 32 50 21 10 23]. A single call, append(ages, 10, 23), would give the same result.
A gotcha: sharing after append
Because a slice shares its array, two slices built from the same one can interfere when there is spare capacity:
c := make([]int, 3, 5)
d := append(c, 9)
e := append(c, 8)
fmt.Println(d, e)
[0 0 0 8] [0 0 0 8]
Both appends wrote into the same spare slot, so d changed when e was created. When the array is full, append copies, and the slices stay separate. Code should not depend on either case: after x = append(x, v), treat the new x as the only valid name for the data.
Cost
Appending one element is cheap on average. When the array fills up, Go allocates a larger one, roughly doubling at small sizes, and copies the elements across. Growing a slice of n elements by repeated appends therefore takes O(n) time overall, so appending in a loop is fine.
You can watch this with cap. Appending the numbers 0 to 19 one at a time to an empty []int and printing the capacity each time it changed gave 1, 2, 4, 8, 16 and 32 in this Go version. The exact growth rule is an implementation detail, so do not rely on those numbers in your own code. Know only that each reallocation copies the elements, and that a slice you build in a loop does not need to be sized in advance.
Common mistakes
- Dropping the result.
append(ages, 10)alone does not compile. - Appending to an array. Only slices can be appended to. An array such as
[4]inthas a fixed size. - Appending the wrong type. The values must match the element type: you cannot append
"10"to a[]int. - Expecting insertion at the front.
appendonly adds at the end.
Key takeaways
s = append(s, v)addsvat the end; always assign the result.- Several values can be appended in one call, and
other...appends a whole slice. - A nil slice is a fine starting point.
- Appending in a loop is efficient on average.