An array has a fixed length that is part of its type. A slice is a flexible, growable view over an array. In everyday Go you reach for slices almost always, but understanding arrays underneath is what makes slices click.
Arrays: a fixed size
An array’s length is fixed at declaration and cannot change. The size is part of
the type, so [3]string and [4]string are different types.
var days [3]string
days[0] = "Mon"
days[1] = "Tue"
days[2] = "Wed"
Because the length is baked in, arrays are rigid. That is why slices exist.
Slices: a flexible view
A slice literal looks like an array literal without the size. A slice has no fixed length and can grow.
primes := []int{2, 3, 5, 7}
len and cap
len returns how many elements a slice currently holds. cap returns how many
it can hold before it must grow its underlying storage.
s := []int{2, 3, 5, 7}
fmt.Println(len(s), cap(s)) // 4 4
Growing a slice with append
append returns a new slice with the extra elements. Always assign the
result back — usually to the same variable.
s := []int{1, 2}
s = append(s, 3, 4)
// s is now [1 2 3 4]
Slicing with a[low:high]
a[low:high] produces a slice from index low up to, but not including, high.
Either bound can be omitted to mean “the start” or “the end”.
letters := []string{"a", "b", "c", "d", "e"}
fmt.Println(letters[1:3]) // [b c]
fmt.Println(letters[:2]) // [a b]
fmt.Println(letters[3:]) // [d e]
Iterating with range
for range walks a slice, giving the index and a copy of each element.
for i, v := range primes {
fmt.Println(i, v)
}
Creating slices with make
make builds a slice with a given length and, optionally, a capacity. It is the
right tool when you know roughly how many elements you will add.
scores := make([]int, 0, 4) // len 0, cap 4
scores = append(scores, 10) // no reallocation yet
Slices share a backing array
This is the classic gotcha. A slice points at an array, and sub-slices share that same array. Writing through one view changes the other.
base := []int{1, 2, 3, 4}
view := base[1:3] // [2 3]
view[0] = 99
fmt.Println(base) // [1 99 3 4]
A complete example
This program builds a slice, appends to it, takes a sub-slice, and sums values
gathered with make. It compiles and runs as-is.
package main
import "fmt"
func main() {
// A slice literal.
primes := []int{2, 3, 5, 7}
fmt.Println("primes:", primes, "len:", len(primes), "cap:", cap(primes))
// Grow it with append.
primes = append(primes, 11, 13)
fmt.Println("after append:", primes)
// A sub-slice shares the same backing array.
firstThree := primes[:3]
fmt.Println("first three:", firstThree)
// make creates a slice with a length and capacity.
scores := make([]int, 0, 4)
for i := 1; i <= 4; i++ {
scores = append(scores, i*i)
}
// Iterate with range.
sum := 0
for _, s := range scores {
sum += s
}
fmt.Println("scores:", scores, "sum:", sum)
}
Running it prints:
primes: [2 3 5 7] len: 4 cap: 4
after append: [2 3 5 7 11 13]
first three: [2 3 5]
scores: [1 4 9 16] sum: 30
Common mistakes
- Discarding the result of
append.append(s, x)alone loses the new slice; you must writes = append(s, x). - Forgetting the shared backing array. Two slices from the same array see each other’s writes. Copy when you need independence.
- Confusing
lenandcap.lenis what you can read today;capis how far the storage reaches before a reallocation.
Practice
Given numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, build a new slice that
holds only the even numbers and print it.
Show the solution
package main
import "fmt"
func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
evens := make([]int, 0, len(numbers))
for _, n := range numbers {
if n%2 == 0 {
evens = append(evens, n)
}
}
fmt.Println(evens)
}Starting from an empty slice with capacity len(numbers) avoids reallocations,
and the range loop appends each even value, printing [2 4 6 8 10].
Summary
- Arrays have a fixed length; slices are flexible views you use most of the time.
appendreturns a new slice,len/capdescribe it, andmakepre-sizes it.- Slices share a backing array, so
copywhen you need an independent one.
Next, learn about Go’s other core collection, the key-value maps, and revisit iteration in loops.