Slices in Go are one of the most powerful, but also insidious tools of the language. At first glance, they seem to be just "dynamic arrays", but under the hood they have enough nuances to turn one line of code into a bug, which will take several hours to debug. Let's figure out how they actually work.

What the slice consists of
A slice in Go is not an array. It is a "thin wrapper" that stores:
Array pointer — the actual place where data is stored.
Length (len) — how many items are available right now.
Capacity (cap) — how many elements can be accommodated without reallocating memory.
s := []int{1, 2, 3, 4}
fmt.Println(len(s)) // 4
fmt.Println(cap(s)) // 4
Capacity and its role
Capacity is a reserve. When we do append, Go checks:
If there is enough capacity, new elements are simply written to the same array.
If the capacity is over, Go creates new larger array and copies the old data there.
s := make([]int, 2, 4) // len=2, cap=4
s = append(s, 10, 20) // there are still enough places
fmt.Println(cap(s)) // 4
s = append(s, 30) // cap is over → new array
fmt.Println(cap(s)) // 8Copy and pitfalls
The copy function makes a shallow copy:
a := []int{1, 2, 3}
b := make([]int, len(a))
copy(b, a)
b[0] = 99
fmt.Println(a) // [1 2 3]
fmt.Println(b) // [99 2 3]But if you just assign:
a := []int{1, 2, 3}
b := a
b[0] = 99
fmt.Println(a) // [99 2 3] ❗Here a and b point to the same array — a common mistake of beginners.

Grow: how slices grow
Go does not guarantee an exact growth algorithm, but usually works like this:
If the capacity is small, it doubles.
If the capacity is already large, it increases by about 25%.
👉 Therefore, when working with large data, it is better to allocate make([]T, 0, N) in advance with the necessary margin.
Pitfalls: where developers most often burn out
General underlying array
a := []int{1, 2, 3, 4, 5} b := a[:3] c := a[2:] c[0] = 99 fmt.Println(b) // [1 2 99] 😱bandcuse the same array.Growth breaks the "connection"
a := []int{1, 2, 3} b := a a = append(a, 4) // cap exhausted → new array b[0] = 99 fmt.Println(a) // [1 2 3 4] fmt.Println(b) // [99 2 3]After the growth,
aalready has a different array, and the connection withbis lost.Slices of a large array
big := make([]byte, 1e6) small := big[:1] // small looks tiny, but it keeps the entire megabyte in memorySolution:
copyin a new slice.
Total
Slices in Go are a convenient tool, but:
remember about capacity and growth,
use
copyif you want independent data,be careful with "cuts from cuts".
Understanding the structure of the slices under the hood will save you from unexpected bugs and help you write more reliable code.
