Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Go  Module 4 – Arrays, slices, maps and strings

Slices: length, capacity, append and aliasing

How a Go slice points into an array, what len and cap count, when append writes into a shared array or copies to a new one, and how to keep two slices apart.

  • Intermediate
  • 15 minutes
  • Examples run with Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 5 of 15 minutes free
Free: A slice is a pointer, a length and a capacity · append writes into the array while there is room, then copies. With a pass: 2 in-depth parts and 3 interview answers, about 10 minutes.

What you will learn

  • Explain the three parts of a slice, a pointer, a length and a capacity, and read them with len and cap
  • Predict when append writes into the shared array and when it copies to a new one
  • Prevent slices from overwriting each other with full slice expressions, copy and slices.Clone
  • Size slices in advance with make, and choose between a nil slice and an empty one
  • Release the large array that a small slice keeps in memory

Before you start

What you’ll learn · Before you start

What you will learn

  • Explain the three parts of a slice, a pointer, a length and a capacity, and read them with len and cap
  • Predict when append writes into the shared array and when it copies to a new one
  • Prevent slices from overwriting each other with full slice expressions, copy and slices.Clone
  • Size slices in advance with make, and choose between a nil slice and an empty one
  • Release the large array that a small slice keeps in memory

Before you start

On this page

Two appends to one slice can overwrite each other: when dirs holds 3 names with room for 4, append(dirs, "draft.txt") and append(dirs, "final.txt") both come back ending in final.txt. A slice: A view of part of an array, written []T: a pointer to its first element, a length and a capacity. Copying a slice copies only those three fields, so two slices can share elements. owns no elements of its own; it describes part of an array, and append writes into that array whenever it has room.

You will read the three parts of a slice, predict when append shares an array and when it copies, and keep slices apart with a full slice expression. All the output on this page was printed by the programs as they ran with Go 1.27.1.

A slice is a pointer, a length and a capacity

Slicing an array gives a view of some of its elements, and writing through the view writes the array:

window.go: two slices over one array of six Go · window.go
// window.go: a slice describes part of an array with a pointer, a length and a capacity.
package main

import "fmt"

func main() {
	arr := [6]int{10, 20, 30, 40, 50, 60}
	s := arr[1:4] // elements 1, 2 and 3 of arr
	fmt.Println(s, "len", len(s), "cap", cap(s))

	s[0] = 99 // s has no elements of its own: this writes arr[1]
	fmt.Println(arr)

	t := s[:cap(s)] // a slice of s may reach as far as its capacity
	fmt.Println(t, "len", len(t), "cap", cap(t))
}

Output

[20 30 40] len 3 cap 5
[10 99 30 40 50 60]
[99 30 40 50 60] len 5 cap 5

Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run window.go

arr[1:4] reached three elements, but cap said 5. The specification says a slice value holds a length, a capacity and a reference to its underlying array, the backing array: The array that holds a slice's elements. Slices taken from one another share it, so a write through one slice is seen by every slice that reaches the same element.. The length counts the elements the slice can index now; the capacity: The number of elements from a slice's first element to the end of its backing array, reported by cap. append writes into that room before it needs a new array. counts every element from its first one to the end of the array. Step through the program and watch what each slice reaches:

Interactive · step by step Two slices over one array in window.go Step through window.go and watch which elements of arr each slice reaches.
Two slices over one array in window.go, step 5 of 5. Showing s := arr[1:4], t := s[:cap(s)], arr[0], arr[1] (99), arr[2], arr[3], arr[4], arr[5].pointerpointers := arr[1:4]len 3, cap 5t := s[:cap(s)]len 5, cap 5arr[0]10arr[1]99arr[2]30arr[3]40arr[4]50arr[5]60

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. An array of six. arr is an array of six ints, at indexes 0 to 5. It is the only storage in this program; every slice that follows describes part of it.
  2. s := arr[1:4]. s is a slice header of three parts. Its pointer is the address of arr[1], its length is 3, the elements at indexes 1, 2 and 3, and its capacity is 5, every element from arr[1] to the end of the array.
  3. s[0] = 99. s[0] is the element the pointer names, arr[1]. The write lands in the array, so arr prints as [10 99 30 40 50 60].
  4. Up to the capacity. t := s[:cap(s)] keeps the same pointer and takes the length up to the capacity, 5, so t reaches arr[1] to arr[5] and prints [99 30 40 50 60].
  5. Nothing was copied. arr, s and t read and write the same six ints. Only the headers differ, each with its own start, length and capacity.

s[0] = 99 changed arr[1], because a slice has no elements to change except the array’s. And s[:cap(s)] reached two elements that s itself could not index: a slice can be resliced up to its capacity, never past it, so s[:6] would stop the program with the panic slice bounds out of range [:6] with capacity 5. Nothing in the program copied an element; three variables used one array of six ints.

Checkpoint

Check: Pointer, length and capacity

  1. Question 1 of 2 What does this print?

    Read the code, then choose one answer.

    var arr [8]int
    s := arr[2:5]
    fmt.Println(len(s), cap(s))
    Show the answer: Question 1 of 2

    Answer: 3 6

    For arr[low:high] the length is high − low and the capacity is len(arr) − low, as arr[1:4] of six ints had length 3 and capacity 5 in window.go.

  2. Question 2 of 2 What does this print?

    Read the code, then choose one answer.

    nums := []int{1, 2, 3, 4}
    part := nums[1:3]
    part[1] = 30
    fmt.Println(nums)
    Show the answer: Question 2 of 2

    Answer: [1 2 30 4]

    A slice has no elements of its own; s[0] = 99 in window.go changed arr[1] in the same way.

append writes into the array while there is room, then copies

append(s, x) returns a new slice header. If the length of s plus the new elements still fits its capacity, the specification says append re-uses the underlying array: it writes the new elements just past the length and returns a longer view of the same array. Only when they do not fit does it allocate a new, larger array and copy the old elements into it. That is the whole story of the two paths in the opening:

two_appends.go: two appends to dirs, then the fix Go · two_appends.go
// two_appends.go: two appends to one slice with spare room, and the full slice expression that keeps them apart.
package main

import "fmt"

func main() {
	dirs := make([]string, 0, 4)
	dirs = append(dirs, "home", "asha", "notes") // length 3, room for 4
	draft := append(dirs, "draft.txt")
	final := append(dirs, "final.txt")
	fmt.Println(draft)
	fmt.Println(final)

	safe := dirs[:3:3] // the same elements, capacity 3: no spare room
	draft = append(safe, "draft.txt")
	final = append(safe, "final.txt")
	fmt.Println(draft)
	fmt.Println(final)
	fmt.Println("caps:", cap(dirs), cap(safe), cap(draft))
}

Output

[home asha notes final.txt]
[home asha notes final.txt]
[home asha notes draft.txt]
[home asha notes final.txt]
caps: 4 3 6

Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run two_appends.go

Step through both halves of the run:

Interactive · step by step Two appends to dirs, before and after the fix Step through two_appends.go and watch which array each append writes into.
Two appends to dirs, before and after the fix, step 7 of 7. Showing dirs, the array of dirs (home asha notes final.txt), safe := dirs[:3:3], draft, a new array, final, another new array. In focus: another new array.pointerdirslen 3, cap 4the array of dirshome asha notes final.txtsafe := dirs[:3:3]len 3, cap 3draftlen 4, cap 6a new array... notes draft.txtfinallen 4, cap 6another new array... notes final.txt

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Room for one more. dirs holds three names in an array with room for four, so its length is 3 and its capacity is 4.
  2. The first append. append(dirs, "draft.txt") finds room, writes draft.txt into element 3 of the same array, and returns a header of length 4.
  3. The second append. dirs still has length 3, so append(dirs, "final.txt") finds the same free element and writes final.txt over draft.txt.
  4. Both say final.txt. draft and final are two headers over one array, and element 3 holds final.txt, so both print [home asha notes final.txt].
  5. safe := dirs[:3:3]. A full slice expression keeps the three names but sets the capacity to 3, so safe has no free element left.
  6. append copies. append(safe, "draft.txt") finds no room, so it copies the three names into a new array, adds draft.txt there and returns a header of capacity 6.
  7. Each its own array. The second append copies again, into another array, so draft keeps draft.txt and final holds final.txt. Neither write touched the array of dirs.

dirs had length 3 and capacity 4, so both appends wrote into its element 3, and the second write replaced the first: draft and final both printed final.txt. The fix, dirs[:3:3], is a full slice expression: A slice expression with three indexes, s[low:high:max]. The third index sets the capacity of the result to max - low, so a later append cannot write past high into shared elements.: its third index sets the capacity to max - low, here 3, so nothing is left to share and each append copied into an array of its own. slices.Clip(dirs) returns the same dirs[:len(dirs):len(dirs)].

Ignoring what append returns

append(s, x) on a line of its own would throw away the new header, and with it any new array, so the compiler refuses it; for a slice of strings it reports append(s, x) (value of type []string) is not used. Assign the result, normally to the same variable: s = append(s, x).

Assigning the result is also what keeps a slice growing in a loop: each append may hand back a header over a new array, and only the assignment carries it into the next pass.

Key idea

append writes into the backing array while the capacity allows, so two slices made from one base can overwrite each other; take away the spare capacity, or copy, before handing a base to two appends.

Checkpoint

Check: When append shares and when it copies

  1. Question 1 of 2 What does this print?

    Read the code, then choose one answer.

    base := make([]int, 2, 3)
    x := append(base, 10)
    y := append(base, 20)
    fmt.Println(x[2], y[2])
    Show the answer: Question 1 of 2

    Answer: 20 20

    With length 2 and capacity 3, each append writes index 2 of base's array, as draft and final both ended in final.txt in two_appends.go.

  2. Question 2 of 2 Which change, made just before the two appends above, makes x print 10 and y print 20?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: base = base[:2:2]

    Removing the spare capacity, with base[:2:2] or slices.Clip(base), forces every later append to copy, as safe did in two_appends.go.

Sharing a backing array is what makes slicing cheap, and forgetting that two slices share one is what causes aliasing bugs. Three more tools decide how slices share: make([]T, 0, n) makes room for n elements in advance, copy fills an existing slice from another, and slices.Clone gives a slice an array of its own. A nil slice, the zero value of a slice type, has no array at all until the first append makes one.

Growth, preallocation and nil slices, measured Pro: opens with a Pro, Premium or Ultimate pass

How much room append adds, and when to make that room yourself, have measured answers. This part records the capacities Go 1.27.1 chose, counts the allocations that preallocating saves, and shows where a nil slice and an empty one part ways.

In this part:

  • The capacities append chose, recorded on Go 1.27.1
  • make with a capacity: 16 allocations down to 1
  • nil and empty slices: the same to len and append, different to JSON

4 runnable examples (Go) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Aliasing failures reproduced, and the copies that fix them Pro: opens with a Pro, Premium or Ultimate pass

Shared arrays cause bugs that print no error: a copy that copies nothing, a removal that changes the caller’s data, rows that overwrite each other and a few bytes that keep 64 MiB in memory. Each one is reproduced here beside the fix that removes it.

In this part:

  • copy moves as many elements as the shorter slice holds
  • A removal that reuses the caller’s array changes the caller’s data
  • Rows cut from one array overwrite each other
  • Sixteen bytes sliced from a buffer kept 64 MiB in memory

4 runnable examples (Go) · about 5 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Practise

Practice covers the two ideas above, the parts of a slice and what append shares, beginning with the one your earlier answers left weaker. A wrong choice tells you what it misses, and a right one why it holds:

Practice that adapts to you

Practise this lesson

6 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 6 Which three things does a slice value hold?

    Choose one answer.

    Show the answer: Question 1 of 6

    Answer: A pointer into a backing array, a length and a capacity

    A slice is a small header over an array: a pointer, a length and a capacity.

  2. Question 2 of 6 arr is a [10]int. What is cap(arr[3:7])?

    Type a number.

    Show the answer: Question 2 of 6

    Answer: 7 elements

    The capacity runs from the slice's first element, index 3, to the end of the array: 10 − 3 = 7. The length is 7 − 3 = 4.

  3. Question 3 of 6 s := arr[1:4] for an array of six ints, as in window.go. What does s[:6] do?

    Choose one answer.

    Show the answer: Question 3 of 6

    Answer: It panics when the program runs, because 6 is past the capacity of 5

    t := s[:cap(s)] was the furthest window.go could go: five elements, from arr[1] to arr[5].

  4. Question 4 of 6 What does append(s, x) return?

    Choose one answer.

    Show the answer: Question 4 of 6

    Answer: A slice header, over the same array if it had room and over a new one if not

    The compiler refuses an append whose result is not used, because the new header is the only place the new length, and any new array, is kept.

  5. Question 5 of 6 What does this print?

    Read the code, then choose one answer.

    f := []int{1, 2, 3}
    t := append(f, 4)
    t[0] = 100
    fmt.Println(f[0])
    Show the answer: Question 5 of 6

    Answer: 1

    A slice with no spare capacity never shares its array with what append returns, the reason dirs[:3:3] fixed two_appends.go.

  6. Question 6 of 6 func withTag(tags []string) []string { return append(tags, "new") } is called twice on one base slice that has spare capacity, giving a and b. What can go wrong?

    Choose one answer.

    Show the answer: Question 6 of 6

    Answer: Both calls write into the same free element, so a's last tag is overwritten by b's

    A helper that appends to a slice it was given can write into the caller's spare capacity; clip the input, slices.Clip(tags), or copy it before appending when results must be independent.

Interview questions

Warm-up (fresher): What does a slice value hold?

Three things: a pointer to an element of a backing array, a length and a capacity. The elements themselves are in the array, so copying a slice copies only these three fields, and two slices can share the same elements. len reports the length and cap the capacity.

Three longer questions of the module bank build on this lesson:

  1. Fresher to mid

    A function builds two queries from one base slice of filters: q1 := append(base, "status=open") and q2 := append(base, "status=closed"). In production both queries end with status=closed, but the unit test, which passes a slice literal as base, passes. Explain both results and fix the function.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A service reads 64 MiB log files whole, keeps the first 32 bytes of each as its header in a map and drops the rest. After 100 files the heap holds over 6 GiB. Explain why, say how you would confirm it, and fix it.

    Model answer, Pro. First step and follow-ups

  3. Fresher to mid

    RemoveAt(s []int, i int) []int returns append(s[:i], s[i+1:]...). A caller removes index 2 from [1 2 3 4 5], prints its own slice and sees [1 2 4 5 5]. Explain the output, then design the function's API.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A slice is a pointer into a backing array with a length and a capacity; slicing copies no elements, and a write through any slice of the array changes the array.
  • The capacity counts from the slice’s first element to the end of the array, and a slice can be resliced up to its capacity.
  • append writes into the backing array while the capacity allows and copies to a new array only when it does not, so two appends to one base can overwrite each other.
  • A full slice expression, s[low:high:max], or slices.Clip, removes the spare capacity, so the next append copies.

Terms in this lesson

backing array
The array that holds a slice's elements. Slices taken from one another share it, so a write through one slice is seen by every slice that reaches the same element.
capacity
The number of elements from a slice's first element to the end of its backing array, reported by cap. append writes into that room before it needs a new array.
full slice expression
A slice expression with three indexes, s[low:high:max]. The third index sets the capacity of the result to max - low, so a later append cannot write past high into shared elements.
slice
A view of part of an array, written []T: a pointer to its first element, a length and a capacity. Copying a slice copies only those three fields, so two slices can share elements.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.