languages/go-slices-why-appending-to-a-copy-can-change-the-original.md
Go slices: why appending to a copy can change the original
Change a copy of a Go slice and sometimes the original changes too. The reason is that a slice is only a small window onto an array, with a start, a length and a capacity.
Step through `append` yourself: with room to spare it writes into the same array, without room it makes a new one. Real Go 1.24 output shows both cases, plus how to copy safely and cap a window so it cannot overwrite its neighbour.

The surprise
You copy a list, change the copy, and the original changes too. Or you do the same thing and the original does not change. In Go, both can happen with the very same line of code, and the difference is a number you may not have looked at yet.
Go's flexible list type is called a slice. It is the thing you reach for in almost every Go program, so it is worth understanding what one really is.
A slice is a small window onto an array
Go has fixed-size arrays: a row of boxes that can never grow. A slice does not hold values itself. It is a tiny note with three facts: where in an array it starts, its length (len, how many boxes it shows) and its capacity (cap, how many boxes are available from its start to the end of the array).
Two slices can point at the same array. Then they are two windows onto the same boxes, and writing through one is visible through the other.
What append does
append(a, 4) adds a value after the last visible element. There are exactly two cases:
1. There is room (len < cap)
Go writes the value into the next spare box of the same array and returns a slice with len one higher. Nothing is copied. The old and new slices now share boxes.
2. There is no room (len == cap)
Go allocates a bigger array, copies the old values over, adds the new one, and returns a slice pointing at the new array. From then on the two slices are independent.
Try it
Pick the capacity, then run the lines in order and watch which boxes are shared.
The same thing in real code
Here is the "room" case as a complete program. We ran it with Go 1.24:
a := make([]int, 3, 4) // len 3, cap 4
a[0], a[1], a[2] = 1, 2, 3
b := append(a, 4) // room left: same array
b[0] = 99
fmt.Println(a, b) // [99 2 3] [99 2 3 4]
c := append(b, 5) // b is full: new array
c[0] = -1
fmt.Println(b, c) // [99 2 3 4] [-1 2 3 4 5]
Notice that a changed when we wrote to b, but b did not change when we wrote to c. The first append had room; the second did not.
A slice written as a literal has no spare room at all:
x := []int{1, 2, 3} // len 3, cap 3
y := append(x, 4) // no room: new array
y[0] = 99
fmt.Println(x, y) // [1 2 3] [99 2 3 4]
Here y got a fresh array, so x is untouched. With Go 1.24, cap(y) printed 6, not 4: when Go grows an array it often takes more than it needs, and the exact amount is up to the runtime, so never rely on a specific number.
Why it grows the way it does
If every append allocated an array exactly one box bigger, appending 1,000 items would copy values over and over. So Go over-allocates. Appending 20 numbers one at a time to an empty slice made the capacity go through 1 2 4 8 16 32 in our run: it roughly doubles for small slices. Most appends are then cheap, and the occasional copy is spread across many of them.
What this means for your code
Always use the result of append. Write a = append(a, v). The returned slice may point at a new array, and the old one may or may not have changed.
Do not assume a slice you were handed is yours. If a function appends to a slice parameter, it might write into spare boxes that belong to the caller's array. When you need an independent copy, make one on purpose:
copyOfA := make([]int, len(a))
copy(copyOfA, a)
You can cap the window. The form a[:2:2] gives a slice with len 2 and cap 2, so appending to it must allocate a new array instead of overwriting a's third box. In our run, d := a[:2:2] then append(d, 50) left a as [99 7 3].
The reverse trap is real too: append(a[:1], 7) wrote 7 into a's second box, because a[:1] still had spare capacity. That is the same rule as before, just in a less obvious place.
Remember
A slice is a window (start, length, capacity) onto an array. append reuses the array when there is room and makes a new one when there is not. If two slices might share an array, check cap before you trust that they are separate.