Code of the Day
BeginnerCore syntax

Arrays, slices, and maps

Fixed-size arrays, dynamic slices with make and append, and key-value maps — Go's built-in collection types.

GoBeginner13 min read
Recommended first
By the end of this lesson you will be able to:
  • Declare and use fixed-size arrays with [n]T syntax
  • Create and grow slices using make and append
  • Explain the relationship between a slice's length and capacity
  • Declare, populate, and query a map with map[K]V
  • Delete a key from a map and check whether a key exists

Every program eventually needs to work with collections of data — lists of items, lookups by name, sequences to iterate. Go's built-in collection types are lean but powerful. There are three to learn at the beginner level: arrays (fixed size, rarely used directly), slices (the workhorse of Go programs), and maps (key-value lookup tables). Understanding slices well is one of the highest-leverage things you can do early in Go.

Arrays

An array is a fixed-size sequence of elements of a single type. The size is part of the type:

var scores [3]int           // [0, 0, 0] — zero-valued
scores[0] = 42
scores[1] = 85
scores[2] = 91

fmt.Println(scores)         // [42 85 91]
fmt.Println(len(scores))    // 3

An array literal initialises all elements at once:

primes := [5]int{2, 3, 5, 7, 11}

Use ... to let the compiler count the elements:

primes := [...]int{2, 3, 5, 7, 11}   // still [5]int

Arrays are values, not references. When you assign an array to a new variable or pass it to a function, Go copies all the elements. For large arrays this is expensive. In practice, most Go code uses slices instead of arrays directly. Arrays are the foundation that slices are built on.

Slices

A is a dynamically-sized view into an underlying array. The type is []T (no number in the brackets). You'll use slices constantly.

Slice literals

fruits := []string{"apple", "banana", "cherry"}
fmt.Println(fruits[0])   // apple
fmt.Println(len(fruits)) // 3

make — create with a given length and capacity

make([]T, length, capacity) allocates a slice with the specified length and optional capacity:

s := make([]int, 3)       // [0, 0, 0], len=3, cap=3
s := make([]int, 3, 10)   // [0, 0, 0], len=3, cap=10

Length is the number of elements currently in the slice. Capacity is the size of the underlying array — how many elements can be added before a reallocation is needed.

append — growing a slice

append adds elements to a slice and returns the new slice:

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

Always assign the result of append back to the slice variable. When the slice exceeds its capacity, Go allocates a new, larger array and copies the data — the original slice variable would point at the old array if you don't reassign.

append returns a new slice header. The pattern s = append(s, val) is idiomatic for a reason — append may or may not allocate a new array underneath. If it does, the old variable still points at the old memory. Always write s = append(s, ...).

Slicing a slice

You can take a sub-slice with the [low:high] syntax, which is half-open ([low, high)):

s := []int{10, 20, 30, 40, 50}
fmt.Println(s[1:3])   // [20 30]   (indices 1 and 2)
fmt.Println(s[:2])    // [10 20]   (from the start)
fmt.Println(s[3:])    // [40 50]   (to the end)

Sub-slices share the underlying array with the original. Modifying elements through a sub-slice modifies the original data. Use copy if you need an independent copy.

len and cap

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

len is how many elements are accessible. cap is how far the underlying array extends. You rarely need cap at the beginner level — len is what you use for bounds and loops.

Maps

A map is an unordered key-value store. The type is map[KeyType]ValueType:

ages := map[string]int{
    "Alice": 30,
    "Bob":   25,
    "Carol": 28,
}

fmt.Println(ages["Alice"])   // 30

make for maps

Use make to create an empty map:

config := make(map[string]string)
config["host"] = "localhost"
config["port"] = "8080"

A map literal with no entries also works: config := map[string]string{}.

Adding, updating, and deleting

ages["Dave"] = 35      // add a new key
ages["Alice"] = 31     // update existing key
delete(ages, "Bob")    // remove a key

delete is a built-in function. It does nothing if the key doesn't exist — no error.

Checking whether a key exists

Map lookup returns a for missing keys, which can be ambiguous (is ages["Unknown"] zero because the key is missing, or because Alice is actually 0 years old?). Use the two-value form to distinguish:

age, ok := ages["Alice"]
if ok {
    fmt.Println("Alice is", age)
} else {
    fmt.Println("Alice not found")
}

The second return value ok is true if the key was present, false if it wasn't. You'll see this comma-ok idiom in several other Go contexts (type assertions, channel receives).

Maps must be initialised before use. A map variable declared with var m map[string]int is nil — reading from it is safe (returns zero values) but writing to it panics. Always initialise with make or a map literal before storing anything.

Check your understanding

Knowledge check

  1. 1.
    Why do you write s = append(s, val) instead of just append(s, val)?
  2. 2.
    What does ages["Unknown"] return if "Unknown" is not in the map ages map[string]int?
  3. 3.
    Writing to a nil map in Go causes a runtime panic.

Do it yourself

Write a program that counts word frequencies in a slice of strings:

words := []string{"go", "is", "fast", "go", "is", "simple", "go"}

Use a map[string]int and a for range loop. After counting, iterate the map and print each word and its count. Run go run main.go and verify that "go" appears 3 times.

Where to go next

You know Go's three core collection types and how to use them idiomatically. Next: structs and methods — how to define your own data types and attach behaviour to them, which is Go's answer to classes.

Finished reading? Mark it complete to track your progress.

On this page