Start learning free

LESSON 99 OF 338 · ARRAYS I

Slices vs Arrays in Go: Length, Capacity and Aliasing

Lesson Read the full explanation

Go has two list-like types, and mixing them up is a rite of passage. An array has a fixed size that is part of its type: [3]int holds exactly three ints, forever. A slice — []int, no number in the brackets — is what you'll actually use almost every time: a flexible window onto a hidden backing array.

A slice has two measurements:

  • len(s) — how many elements you can see right now
  • cap(s) — how many elements fit in the backing array before Go must allocate a bigger one

You grow a slice with append. When the backing array is full, append allocates a bigger array, copies everything over, and returns a NEW slice — so you must always keep the result:

nums := []int{10, 20, 30}   // len 3, cap 3 — full
nums = append(nums, 40)     // new backing array: len 4, cap 6

The trap, part 1: arrays don't grow.

Write [3]int by accident and append refuses to compile:

arr := [3]int{10, 20, 30}   // fixed-size ARRAY, not a slice
arr = append(arr, 40)
./main.go:7:15: first argument to append must be a slice; have arr (variable of type [3]int)

Read it once, remember it forever — the fix is almost always that you wanted []int.


The trap, part 2: slicing SHARES memory.

s[a:b] does not copy anything. It returns a smaller window onto the SAME backing array — indices a up to (but not including) b. Mutate through the window and the original changes with it:

window := nums[1:3]   // a view of nums[1] and nums[2] — no copy
window[0] = 99        // ...this writes straight into nums[1]!

No error, no warning — just an "impossible" value appearing in a slice you never touched. This aliasing surprise bites every Go beginner exactly once. After this lesson, you'll have had yours.


Assignment
Follow the Coding Tutorial and prove all of it to yourself:

  • create nums := []int{10, 20, 30}, then print its len and cap,
  • append the value 40 and print both again — watch cap jump to 6,
  • slice out window := nums[1:3] and print it,
  • set window[0] = 99, then print nums — the original changed.

The exact expected output is:

len = 3 cap = 3
after append: len = 4 cap = 6
window = [20 30]
nums = [10 99 30 40]

Hint
append returns the (possibly brand-new) slice — always assign it back: nums = append(nums, 40).


STEP 1 OF 8
    Ctrl + Enter to check

    GO · ARRAYS I

    Slices Vs Arrays in Go, explained

    Two list types, one you will actually use

    Go has two list-like types that look almost the same. An array has a fixed size that is part of its type: [3]int holds exactly three ints and can never hold four. A slice, written []int with nothing between the brackets, is a window onto an array that Go manages for you. Almost all everyday Go code uses slices.

    The size is the whole difference in the type. [3]int and [4]int are different types, so you can't assign one to the other. Every []int is the same type, whatever it currently holds.

    Length and capacity

    A slice has two measurements:

    • len(s) is how many elements you can see right now.
    • cap(s) is how many elements fit in the backing array, counting from the slice's first element, before Go must allocate a bigger one.

    A literal such as []int{10, 20, 30} creates a backing array of exactly three elements, so both numbers are 3. The array is full.

    Growing with append

    append adds elements to the end. When there is spare capacity it writes into the existing array. When the array is full, it allocates a larger one, copies the elements over and returns a slice that points to the new array. Either way, the result is what you must keep:

    nums := []int{10, 20, 30}
    nums = append(nums, 40)
    fmt.Println(len(nums), cap(nums)) // 4 6
    

    Forgetting to assign the result back is a common mistake: append(nums, 40) on its own leaves nums unchanged (and the compiler rejects an unused result). With the Go 1.22 runtime, growing a full slice of three ints makes room for six, which is why the capacity jumps from 3 to 6. Treat the exact growth numbers as an implementation detail and never depend on them in real programs. The lesson prints them only to show that a new array was allocated.

    Trap 1: arrays don't grow

    If you write the size by accident, append refuses to compile:

    arr := [3]int{10, 20, 30}
    arr = append(arr, 40)
    
    first argument to append must be a slice; have arr (variable of type [3]int)
    

    The fix is almost always to remove the number so that you have a []int. Arrays also behave differently when copied. Assigning an array to another variable copies all of its elements, while assigning a slice copies only the small header that points to the shared backing array:

    arr := [3]int{10, 20, 30}
    b := arr
    b[0] = 1
    fmt.Println(arr, b) // [10 20 30] [1 20 30]
    
    a := []int{1, 2, 3}
    c := a
    c[0] = 100
    fmt.Println(a, c) // [100 2 3] [100 2 3]
    

    Trap 2: slicing shares memory

    The expression s[a:b] copies nothing. It returns a new slice that views elements a up to, but not including, b of the same backing array. So nums[1:3] has two elements, and writing through it changes the original:

    window := nums[1:3]
    window[0] = 99 // the same memory as nums[1]
    

    Nothing warns you, and a value you never touched in nums changes. This is called aliasing. When you need an independent copy, make one on purpose:

    safe := make([]int, len(nums))
    copy(safe, nums)
    safe[0] = -1 // nums is not affected
    

    Capacity adds a second wrinkle. A slice's capacity can extend past its length, and append uses that spare room without allocating. Two slices that share an array can then overwrite each other's appended values:

    base := make([]int, 3, 10)
    w := append(base, 7)
    w2 := append(base, 8)
    fmt.Println(w, w2) // [0 0 0 8] [0 0 0 8]
    

    Both appends wrote into the same slot, so the second overwrote the first. Reslicing with a full slice expression such as nums[1:3:3] caps the capacity at the end of the window, which forces the next append to allocate a new array instead.

    The exercise, step by step

    1. Create the slice. nums := []int{10, 20, 30} with empty brackets, so it can grow.
    2. Print the length and capacity. Pass the labels and numbers as separate arguments to fmt.Println, which separates arguments with a space.
    3. Append 40 and keep the result by assigning it back to nums.
    4. Print both numbers again. The length is now 4 and the capacity 6, proof that a bigger array was allocated.
    5. Take a window with nums[1:3]. Index 3 is excluded, so the window holds 20 and 30.
    6. Print the window to confirm it is [20 30].
    7. Assign 99 to window[0]. No := is needed, since this changes an existing element.
    8. Print nums. The 99 appears at index 1, although you never wrote to nums directly.

    The solution in Go

    nums := []int{10, 20, 30}
    fmt.Println("len =", len(nums), "cap =", cap(nums))
    nums = append(nums, 40)
    fmt.Println("after append: len =", len(nums), "cap =", cap(nums))
    window := nums[1:3]
    fmt.Println("window =", window)
    window[0] = 99
    fmt.Println("nums =", nums)
    

    The output is:

    len = 3 cap = 3
    after append: len = 4 cap = 6
    window = [20 30]
    nums = [10 99 30 40]
    

    Note the order of the steps. The window is taken after the append, so it points into the new, larger array, and nums still shares that array. Had the window been taken before the append, the append would have moved nums to a new array, and writing to the window would no longer have changed it.

    Key takeaways

    • An array's size is part of its type and can't change. A slice is a view with a length and a capacity.
    • Always keep the result of append: nums = append(nums, x).
    • Slicing with s[a:b] copies nothing. Writes through the new slice show up in the original.
    • Use copy into a fresh slice when you need independent data.
    • If the compiler says first argument to append must be a slice, you wrote [3]int where you wanted []int.
    Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free