{}const=>[]async()letfn</>var
Development

Slices under the microscope: capacity, copy, grow, and traps in Go

We analyze slices in Go under the hood: how they are arranged, why capacity decides, what are the dangers of common arrays and how to avoid hidden bugs.

К

Kodik

Author

3 min read

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
🔥 100,000+ students already with us

Tired of reading theory?
Time to code!

Kodik — an app where you learn to code through practice. AI mentor, interactive lessons, real projects.

🤖 AI 24/7
🎓 Certificates
💰 Free
🚀 Start learning
Joined today

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))    // 8

Copy 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

  1. 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] 😱

    b and c use the same array.

  2. 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, a already has a different array, and the connection with b is lost.

  3. Slices of a large array

     big := make([]byte, 1e6)
     small := big[:1]
     // small looks tiny, but it keeps the entire megabyte in memory

    Solution: copy in a new slice.

Total

Slices in Go are a convenient tool, but:

  • remember about capacity and growth,

  • use copy if 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.

🎯Stop procrastinating

Liked the article?
Time to practice!

In Kodik, you don't just read — you write code immediately. Theory + practice = real skills.

Instant practice
🧠AI explains code
🏆Certificate

No registration • No card