Lesson 7 of 18

Slices

Dynamic Arrays, Done Right

Go has arrays, but you will rarely use them directly. Arrays have a fixed size baked into their type: [5]int and [10]int are different types entirely. Instead, Go gives you slices: a flexible, dynamic view over an underlying array.

Creating Slices

// Slice literal
numbers := []int{1, 2, 3, 4, 5}

// Make a slice with initial length and capacity
data := make([]int, 5)      // len=5, cap=5, filled with zeros
buffer := make([]int, 0, 10) // len=0, cap=10

The difference between [5]int (array) and []int (slice) is that single missing number. Slices are what you want almost every time.

Length and Capacity

Every slice has two properties: length (how many elements it contains) and capacity (how many elements the underlying array can hold before reallocation).

s := make([]int, 3, 10)
fmt.Println(len(s)) // 3
fmt.Println(cap(s)) // 10

"Tea, Earl Grey, hot." The replicator does not give you the whole cargo bay --- just the slice you asked for. Go slices work the same way: a precise view into a larger array.

Append

append adds elements to a slice and returns a new slice. If the underlying array is full, Go allocates a bigger one and copies the data:

s := []int{1, 2, 3}
s = append(s, 4, 5)
// s is now [1, 2, 3, 4, 5]

Always reassign the result of append back to the slice variable. This is not optional. append may return a slice pointing to a completely new array.

Slicing

You can create a new slice from an existing one using the slice operator:

s := []int{0, 1, 2, 3, 4}
a := s[1:3]  // [1, 2]     (from index 1, up to but not including 3)
b := s[:3]   // [0, 1, 2]  (from the start)
c := s[2:]   // [2, 3, 4]  (to the end)

A sub-slice shares the same underlying array. Modifying one affects the other. If you need an independent copy, use copy or append to a new slice.

The copy Builtin

copy copies elements from a source slice into a destination slice and returns the number of elements copied. The destination must already have enough length --- copy does not grow the slice:

src := []int{1, 2, 3, 4, 5}
dst := make([]int, 3)
n := copy(dst, src)
fmt.Println(dst) // [1 2 3]
fmt.Println(n)   // 3 (copied min(len(dst), len(src)) elements)

To make a full independent copy of a slice:

original := []int{10, 20, 30}
clone := make([]int, len(original))
copy(clone, original)
// Modifying clone does not affect original

Nil Slices vs Empty Slices

This is a subtle but important distinction:

var nilSlice []int          // nil slice: no underlying array
emptySlice := []int{}       // empty slice: has an underlying array, but length 0
madeSlice := make([]int, 0) // also empty, not nil

fmt.Println(nilSlice == nil)    // true
fmt.Println(emptySlice == nil)  // false
fmt.Println(len(nilSlice))      // 0
fmt.Println(len(emptySlice))    // 0

Both have length 0 and both work fine with append, range, and len. The difference matters when you serialize to JSON (nil becomes null, empty becomes []) or check for "no data provided" vs "explicitly empty."

In practice: use var s []int (nil) when you might not need a slice at all, and s := []int{} when you need an explicitly empty collection.

Your Task

Write a function sum that takes a []int and returns the sum of all elements.

Write a function filterEvens that takes a []int and returns a new []int containing only the even numbers, in order.

Write a function cloneSlice that takes a []int and returns an independent copy using the copy builtin. Modifying the returned slice must not affect the original.

Go runtime loading...
Loading...
Click "Run" to execute your code.