Often you need to store more than one value of the same type — a list of names, a sequence of scores, a buffer of incoming bytes. Go gives you two tools for this: arrays and slices.
In practice, you’ll use slices 99% of the time. But understanding arrays first makes slices much easier to grasp, because slices are built on top of arrays.
Arrays
An array is a fixed-size sequence of values of the same type. The size is part of the array’s type — [3]int and [5]int are completely different types.
package main
import "fmt"
func main() {
var scores [3]int
scores[0] = 95
scores[1] = 88
scores[2] = 73
fmt.Println(scores)
fmt.Println("First score:", scores[0])
fmt.Println("Length:", len(scores))
}
go run main.go
[95 88 73]
First score: 95
Length: 3
Things to notice:
- Index starts at 0, not 1.
scores[0]is the first element. This is universal across programming languages. len(scores)returns the array’s length.lenis a built-in function you’ll use constantly.- Out-of-range access is an error.
scores[3]would fail — if Go can see the index is out of range at compile time (like this literal3), the compiler rejects it; otherwise it panics at runtime.
You can also declare and fill an array in one go:
// inside main()
fruits := [3]string{"apple", "banana", "cherry"}
fmt.Println(fruits)
Or let Go count for you with ...:
// inside main()
days := [...]string{"Mon", "Tue", "Wed", "Thu", "Fri"}
fmt.Println(len(days)) // 5
The problem with arrays
Arrays have a fixed size. If you want to add a sixth day, you can’t — the array’s type is [5]string. You’d need to create a new, bigger array and copy the values. That’s tedious. That’s why arrays are rare in everyday Go code, and slices are everywhere.
Slices
A slice is a flexible, growable view into an array. Slice types don’t include a size — []int is just “a slice of ints”, regardless of length.
package main
import "fmt"
func main() {
scores := []int{95, 88, 73}
fmt.Println(scores)
fmt.Println("Length:", len(scores))
fmt.Println("Capacity:", cap(scores))
}
[95 88 73]
Length: 3
Capacity: 3
Notice the empty [] instead of [3] — that’s the syntax for “slice, not array.”
Growing a slice with append
The most useful function in Go is append. It adds an element to a slice and returns a new slice:
// inside main()
scores := []int{95, 88, 73}
scores = append(scores, 100)
scores = append(scores, 64, 71) // append takes any number of elements
fmt.Println(scores)
[95 88 73 100 64 71]
Always assign the result of
appendback to the slice. Sometimesappendreuses the same underlying array, sometimes it allocates a bigger one — you don’t know which, so you must capture the return value.
Reading and modifying
Like arrays, slices use [index] to read and write:
// inside main()
scores := []int{95, 88, 73}
fmt.Println(scores[1]) // 88
scores[1] = 90
fmt.Println(scores) // [95 90 73]
Slicing a slice
The slice’s namesake operation: take a portion of an existing slice using [low:high]. The result includes index low but excludes index high:
// inside main()
nums := []int{10, 20, 30, 40, 50}
fmt.Println(nums[1:4]) // [20 30 40] — indexes 1, 2, 3
fmt.Println(nums[:3]) // [10 20 30] — from start
fmt.Println(nums[2:]) // [30 40 50] — to end
fmt.Println(nums[:]) // [10 20 30 40 50] — everything
This is incredibly common. “Take the first 10 results”, “skip the first row”, “process from this point onwards” — all natural in Go.
Iterating with range
You saw this in the loops lesson, but it’s worth repeating in context:
// inside main()
nums := []int{10, 20, 30}
sum := 0
for _, n := range nums {
sum += n
}
fmt.Println("Sum:", sum)
Sum: 60
Creating slices with make
When you know the size you want up front, make is more efficient than appending repeatedly:
// inside main()
buffer := make([]byte, 1024) // length 1024, all zeros
fmt.Println("Length:", len(buffer))
You can also specify a separate capacity — the size of the underlying array — which lets you avoid reallocation as the slice grows:
// inside main()
results := make([]int, 0, 100) // length 0, capacity 100
for i := 0; i < 100; i++ {
results = append(results, i*i)
}
This is an optimization. For most code, []T{} or make([]T, 0) is fine.
A practical example
A complete program that reads a list of numbers, finds the sum and the largest:
package main
import "fmt"
func main() {
nums := []int{12, 7, 25, 3, 19, 8, 31, 14}
sum := 0
largest := nums[0]
for _, n := range nums {
sum += n
if n > largest {
largest = n
}
}
fmt.Println("Sum: ", sum)
fmt.Println("Largest:", largest)
fmt.Println("Count: ", len(nums))
}
Sum: 119
Largest: 31
Count: 8
Arrays vs slices, summarized
| Feature | Array | Slice |
|---|---|---|
| Size | Fixed at compile | Grows at runtime |
| Type | [N]T | []T |
| Common use | Rare | Everywhere |
append works | No | Yes |
When you don’t know which to pick: use a slice. You’ll be right almost every time.
What’s next
Slices store values in order. But sometimes you don’t care about order — you want to look up a value by a name. That’s what maps are for.