No pop, but re-slicing
Many languages give lists a pop method. Go slices do not have one. Instead, you shorten a slice by re-slicing it: building a new slice value that covers only the part you want to keep. The slice expression s[:k] keeps the first k elements, so to drop the last one you keep len(s) - 1 of them:
threeArr := []int{1, 4, 6}
threeArr = threeArr[:len(threeArr)-1]
fmt.Println(threeArr)
threeArr = threeArr[:len(threeArr)-1]
fmt.Println(threeArr)
[1 4]
[1]
The upper bound of a slice expression is exclusive, like the end of a range. s[:3] keeps indices 0, 1 and 2. Because len(threeArr) is evaluated again on the second statement, after the first one made the slice shorter, it gives 2 the second time and the slice shrinks by one more element.
Nothing is copied
Re-slicing does not copy any data. The shorter slice refers to the same underlying array as before, with a smaller length. The "removed" value is still in that array, just outside the slice:
w := []int{1, 4, 6}
x := w[:len(w)-1]
fmt.Println(x, len(x), cap(x), w)
x = append(x, 99)
fmt.Println(w)
[1 4] 2 3 [1 4 6]
[1 4 99]
The capacity of x is still 3, so appending to it reuses the freed slot, and that overwrote the 6 in w. If you shorten a slice and also keep another name for the original, the two interact. Usually you assign the result back to the same variable, as the examples above do, and the problem does not arise.
Getting the removed value
A pop in other languages also returns the value. In Go you read the last element first, then shorten:
last := ages[len(ages)-1]
ages = ages[:len(ages)-1]
This is how a stack is usually written in Go: append to push and these two lines to pop.
A complete stack that pushes three numbers and pops them until it is empty looks like this:
var stack []int
stack = append(stack, 1)
stack = append(stack, 2)
stack = append(stack, 3)
for len(stack) > 0 {
top := stack[len(stack)-1]
stack = stack[:len(stack)-1]
fmt.Println(top)
}
3
2
1
The last value pushed is the first one out. The loop condition len(stack) > 0 doubles as the guard against popping from an empty slice.
The exercise
The starting slices are:
fruits := []string{"mango", "kiwi", "grapefruit"}
ages := []int{14, 32, 50, 21}
- Shorten
fruitsby one, assigning the result back tofruits. - Shorten
agestwice in a row, assigning back each time.
fruits = fruits[:len(fruits)-1]
ages = ages[:len(ages)-1]
ages = ages[:len(ages)-1]
Afterwards fruits is [mango kiwi] and ages is [14 32]. A single statement ages[:len(ages)-2] would give the same result. The lesson uses two lines to show that each one sees the slice as the previous line left it.
Removing from the front
The same idea works at the other end. s[1:] drops the first element, and it also shares the array and copies nothing. Both operations take constant time, no matter how long the slice is. Removing from the middle is different, because the elements after the gap have to move; that takes the append function with ... and costs time proportional to the length.
Empty slices
Removing from an empty slice is an error. With length 0, s[:len(s)-1] becomes s[:-1], and the program panics with slice bounds out of range [:-1]. Guard the removal when the slice might be empty:
if len(ages) > 0 {
ages = ages[:len(ages)-1]
}
Common mistakes
- Not assigning the result.
ages[:len(ages)-1]on its own line does not changeages, and the compiler rejects the unused expression. - Using the wrong bound.
s[:len(s)]keeps everything, ands[:len(s)-1]drops one. The bound is the number of elements you keep, not the index you remove. - Expecting memory to be freed. The array stays alive as long as any slice refers to it.
- Removing from an empty slice. Check the length first.
Key takeaways
- Go has no
pop; uses = s[:len(s)-1]to drop the last element. - The result must be assigned back; re-slicing copies nothing and shares the array.
- Read the last element first if you need its value.
- Never shorten a slice whose length is 0.