Arrays & Slices

Fixed arrays, dynamic slices, append, copy, slice expressions, and memory layout.

Beginner 30 min read 🐹 Go

Arrays — Fixed Size

Go arrays have a fixed size that's part of the type. [3]int and [5]int are different types. Arrays are value types — assigning or passing copies the entire array. In practice, you rarely use arrays directly; slices are much more common.

// Declare and initialize
var a [5]int                    // [0 0 0 0 0] (zero values)
b := [3]string{"go", "is", "fun"}
c := [...]int{10, 20, 30}      // Size inferred from elements

fmt.Println(a)
fmt.Println(b)
fmt.Println(len(c)) // 3
Output
[0 0 0 0 0]
[go is fun]
3

Slices — Dynamic, Powerful

Slices are Go's workhorse data structure. They're dynamic-length views into underlying arrays. A slice has three components: a pointer to the array, a length (how many elements), and a capacity (how many elements the underlying array can hold).

// Create slices
s := []int{1, 2, 3, 4, 5}         // Slice literal (no size = slice, not array)
s2 := make([]int, 5)               // make(type, length) - [0 0 0 0 0]
s3 := make([]int, 0, 10)           // make(type, length, capacity)

fmt.Printf("s:  len=%d cap=%d %v\n", len(s), cap(s), s)
fmt.Printf("s3: len=%d cap=%d %v\n", len(s3), cap(s3), s3)
Output
s:  len=5 cap=5 [1 2 3 4 5]
s3: len=0 cap=10 []

append — Growing Slices

s := []int{1, 2, 3}
s = append(s, 4)           // Add one element
s = append(s, 5, 6, 7)     // Add multiple
fmt.Println(s) // [1 2 3 4 5 6 7]

// Append another slice with ...
more := []int{8, 9}
s = append(s, more...)
fmt.Println(s) // [1 2 3 4 5 6 7 8 9]

When append exceeds the capacity, Go allocates a new, larger underlying array and copies elements. This is amortized O(1) but can cause unexpected behavior if you hold references to the old slice.

Slicing — Creating Sub-slices

s := []int{0, 1, 2, 3, 4, 5}
fmt.Println(s[1:4])  // [1 2 3] (index 1 to 3, exclusive end)
fmt.Println(s[:3])   // [0 1 2] (first 3)
fmt.Println(s[3:])   // [3 4 5] (from index 3)
fmt.Println(s[:])    // [0 1 2 3 4 5] (copy of entire slice)

⚠️ Common Mistake: Sub-slices share memory

A sub-slice shares the same underlying array. Modifying s[1:3] also modifies s! To get an independent copy, use copy():

original := []int{1, 2, 3, 4, 5}
independent := make([]int, 3)
copy(independent, original[1:4]) // independent copy of [2 3 4]
Key Takeaway: Use slices, not arrays. Create with make() when you know the size, or literals when you have values. Always reassign the result of append: s = append(s, val).
Array vs Slice Memory Layout
Array [5]int (fixed, value type):
10
0
20
1
30
2
40
3
50
4
Slice []int (dynamic, reference type):
20
ptr
30
len=3
40
cap=4

Practice Exercises

Easy Hello World Variant

Modify the example to accept user input and print a personalized greeting.

Easy Code Reading

Read through the code examples above and predict the output before running them.

Medium Extend the Example

Take one code example and add error handling, input validation, or a new feature.