A slice in one example
A slice is a growable view onto an array. You create one with a literal or with make, grow it with append, and read its size with len.
[]string has no length in the brackets. That is the difference from an array type like [4]string, whose size is fixed.
What a slice is
A slice value is a small header with three fields:
- a pointer to an element in a backing array
- a length: how many elements the slice can see
- a capacity: how many elements exist from that pointer to the end of the backing array
Copying a slice (assigning it, passing it to a function) copies only this header. Both copies point at the same elements. Almost every slice surprise comes from forgetting that.
make: length and capacity
make([]T, len, cap) allocates a backing array and returns a slice over it. The capacity is optional and defaults to the length.
A common mix-up: make([]int, 5) followed by five append calls gives you ten elements, the first five of them zero. Use make([]int, 0, 5) when you plan to append, or make([]int, 5) when you plan to assign by index.
Preallocating capacity when you know the final size avoids repeated growth. It is an optimization, not a requirement.
append and growth
append adds elements after the current length. If the capacity is big enough, it writes in place. If not, it allocates a larger array, copies the old elements over, and returns a slice pointing at the new array.
The capacity roughly doubles while the slice is small and grows by a smaller factor (moving toward 1.25x) once it passes 256 elements. The exact numbers are a runtime detail that has changed between Go versions, so never write code that depends on them.
Because append may return a different array, you must use its result:
append(s, 4) // compile error: append(s, 4) (value of type []int) is not used
s = append(s, 4) // correct
Appending a whole slice uses the spread syntax: s = append(s, other...).
Slicing shares the backing array
s[low:high] makes a new slice header over the same array, covering indexes low up to but not including high. Either bound can be left out. No elements are copied.
Output:
[10 99 30 40 50]
3 4
That sharing is what makes slicing cheap. It is also the source of the most famous slice bug.
The append aliasing bug
When a sub-slice has spare capacity, appending to it writes into the parent's array, over elements the parent still uses.
Output:
[1 2 100 4 5]
[1 2 100]
[cmd test] [cmd test]
a and b share the array slot after "cmd", so the second append overwrote the first. The code looks fine and works whenever the capacity happens to be full, which is why this bug shows up only sometimes.
Two fixes:
- The full slice expression
s[low:high:max]caps the capacity atmax-low. With no spare capacity, the nextappendmust allocate.first := base[:2:2]makes the first example safe. - Copy explicitly when a slice will outlive the call or be appended to independently:
slices.Clone(prefix)orappend([]string(nil), prefix...).
A related trap: a small slice of a huge array keeps the whole array alive for the garbage collector. If you read a 100 MB file and keep data[:10], the 100 MB stays in memory. Clone the part you keep.
copy
The built-in copy(dst, src) copies min(len(dst), len(src)) elements and returns that count. It never grows dst.
copy handles overlapping source and destination correctly, so copy(s[1:], s) shifts elements right without corruption.
The slices package
Since Go 1.21 the standard slices package has generic helpers for the operations you used to write by hand.
| Function | What it does |
|---|---|
Contains, Index | find a value (ContainsFunc, IndexFunc take a predicate) |
Sort, SortFunc, SortStableFunc | sort in place |
BinarySearch | search a sorted slice |
Insert, Delete, DeleteFunc | insert or remove, return the new slice |
Compact | remove consecutive duplicates |
Equal, Compare | compare element by element |
Clone, Reverse, Max, Min | the obvious |
Collect, Sorted, Values, All | work with iterators (Go 1.23) |
Delete and Insert return a slice you must assign, like append. Sorting has its own page: sorting.
Removing elements while looping by index skips elements. Use slices.DeleteFunc instead:
s = slices.DeleteFunc(s, func(n int) bool { return n%2 == 0 }) // drop evens
2D slices
A slice of slices gives you a grid whose rows can differ in length. Each row must be allocated separately.
Forgetting the inner make leaves each row nil, and grid[1][2] = 7 panics with index out of range [2] with length 0.
nil slices and empty slices
A nil slice behaves like an empty one for len, cap, range and append, so prefer var s []T as the zero state. Initialize to []T{} only when the difference matters, which is mostly JSON output (null vs []). Check emptiness with len(s) == 0, not s == nil.
Slices and functions
A function receiving a slice can change its elements, and the caller sees the changes. It cannot change the caller's length, because it received a copy of the header. A function that appends must return the new slice:
func addAll(s []int, vals ...int) []int {
return append(s, vals...)
}
This is why append, slices.Delete and slices.Insert all return a slice.
Common mistakes
- Ignoring
append's result. Does not compile when discarded entirely, butappend(s, x)assigned to a different variable than the one you keep using is a logic bug. - Two appends from one base. They may share storage. Clone the base or use a full slice expression.
make([]T, n)thenappend. Leaves n zeros at the front.- Index out of range.
s[len(s)]panics. The last element iss[len(s)-1]. - Comparing slices with
==. Onlys == nilcompiles. Useslices.Equal.
Frequently Asked Questions
What is the difference between length and capacity of a slice in Go?
len(s) is how many elements the slice currently holds. cap(s) is how many elements fit in the backing array starting at the slice's first element. append writes into the spare capacity when there is some, and allocates a new, larger array when there is not.
How do you append to a slice in Go?
Call the built-in append and assign the result back: s = append(s, x). It can add several values (append(s, 1, 2, 3)) or another slice (append(s, other...)). Always use the return value, because append may return a slice that points at a new array.
How do I remove an element from a slice in Go?
Use slices.Delete(s, i, i+1) (Go 1.21), which shifts later elements down and returns the shorter slice. Since Go 1.22 it also zeroes the freed tail slots. If order does not matter, swap with the last element and truncate: s[i] = s[len(s)-1]; s = s[:len(s)-1].
How do I check if a slice contains a value in Go?
Use slices.Contains(s, v) from the standard slices package (Go 1.21). slices.Index(s, v) returns the position or -1, and slices.ContainsFunc takes a predicate. For repeated lookups on large data, build a map instead.
What is the difference between a nil slice and an empty slice in Go?
var s []int is nil; s := []int{} is empty but not nil. Both have length 0, both work with len, range and append. The difference shows up in s == nil checks and in encoding/json, which encodes a nil slice as null and an empty slice as [].