Learn Go← Dashboard

Fixed-size arrays and the workhorse slice type.

Slice Internals (Length and Capacity)

Understanding the relationship between len, cap, and the underlying array makes a lot of Go idioms make sense.

A slice is a header

You can picture a slice as three fields:

┌─ slice s
│   ptr  ──────►  [ _ , _ , _ , _ , _ , _ , _ ]    // backing array
│   len  = 3
│   cap  = 7
└──
  • len(s) — how many elements you can actually read with s[i].
  • cap(s) — how many slots the backing array has starting from the slice's first element.
  • When append runs out of capacity, it allocates a bigger array and copies.
go playground
Loading...

You'll see capacity stay at 4 until it overflows, then double (in this implementation; the growth factor isn't part of the language spec).

s[low:high:max] — capping a slice

The three-index slice expression lets you control the capacity of the result:

s := []int{0, 1, 2, 3, 4}
view := s[1:3:3]    // len=2, cap=2

This is useful when you want to prevent later append calls from accidentally clobbering elements past the end:

go playground
Loading...

nil slice vs empty slice

var a []int          // nil — ptr=nil, len=0, cap=0
b := []int{}         // not nil, but len=0, cap=0

Both have len(a) == 0. You can append to either. The difference shows up in comparisons (a == nil is true, b == nil is false) and when serializing to JSON (nil → null; []int{}[]). Prefer nil unless you specifically need the "empty but not nil" distinction.

Pre-allocate when you know the size

If you'll append n items, give make a capacity:

ids := make([]int, 0, len(users))   // pre-sized — no reallocations
for _, u := range users {
    ids = append(ids, u.ID)
}

This avoids the doubling-and-copying that happens when capacity runs out — a free speedup whenever you know the size in advance.

`s := make([]int, 3, 10)`. What are `len(s)` and `cap(s)`?