Slices
A slice looks like an array without the fixed length, and it's what Go code uses for almost every list of things:
package main
import "fmt"
func main() {
nums := []int{3, 1, 4, 1, 5} // slice literal — no length in the brackets
fmt.Println(nums[0], len(nums)) // 3 5
}
[]int (no number) is a slice of ints. Indexing and len work exactly as
they do on arrays.
A slice is a view, not a container
Under the hood a slice is a small header: a pointer into a backing array,
a length, and a capacity. len(s) is how many elements you can see; cap(s)
is how far the backing array extends past the slice's start.
The slicing expression s[lo:hi] builds a new view of the same array — from
index lo up to but not including hi:
nums := []int{10, 20, 30, 40, 50}
mid := nums[1:4]
fmt.Println(mid) // [20 30 40]
fmt.Println(len(mid), cap(mid)) // 3 4 (cap runs to the array's end)
Omitted bounds default to the ends: nums[:2] is the first two elements,
nums[2:] is everything from index 2, nums[:] is the whole thing.
Because both views share one backing array, writing through one is visible through the other:
mid[0] = 999
fmt.Println(nums) // [10 999 30 40 50]
This is deliberate. Slicing costs nothing — no copying, just a new header — so passing slices around is cheap even when the data is huge. The trade-off is that you must remember slices share; when you truly need independent data, you copy explicitly (next lesson).
nil slices vs empty slices
The zero value of a slice is nil — no backing array at all:
var s []int // nil
t := []int{} // empty, but non-nil
fmt.Println(s == nil, t == nil) // true false
fmt.Println(len(s), len(t)) // 0 0
Here Go is unusually forgiving: len, cap, range, and even append all
work fine on a nil slice. A nil slice behaves like an empty one, so
idiomatic Go rarely distinguishes them — functions check len(s) == 0, not
s == nil. Well-written code (and well-written tests) treats "no elements"
as one condition, whichever form it arrives in.
Building a new slice from an old one
A common pattern: loop over an input slice, keep some elements, return them as a fresh slice.
var out []int // start nil; append will handle it
for _, n := range nums {
if keep(n) {
out = append(out, n)
}
}
return out
We'll dissect append properly in the next lesson — for now, trust the
idiom: out = append(out, n) adds n to the end and hands back the slice to
reassign. The exercise below asks you to filter a slice this way.
Practice
1. Keep the Evens
BeginnerImplement
evensso it returns a new slice containing only the even numbers fromnums, in their original order:func evens(nums []int) []intFor example,
evens([]int{1, 2, 3, 4, 5, 6})returns[2 4 6], andevens([]int{-4, 7, 0})returns[-4 0](zero and negative even numbers count).If there are no even numbers, return a slice of length zero — either
nilor an empty slice is fine, since idiomatic Go treats them the same.Do not modify
nums. Build your result with the append idiom from the lesson: start withvar out []intandout = append(out, n)for each evenn. A number is even whenn%2 == 0.package main import "fmt" // evens returns a new slice containing only the even numbers from nums, // in their original order. It does not modify nums. func evens(nums []int) []int { // TODO: implement return nil } func main() { fmt.Println(evens([]int{1, 2, 3, 4, 5, 6})) }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.