ToolPopToolPop
Go · Lesson 6 of 22

Slices and maps, the two collections you will live in

7 min readUpdated 25 Jun 2026

If you spend a year writing Go, 80% of that year is slices and maps. Arrays exist, but you will almost never use them directly. This lesson is the mental model that prevents the most common Go bugs in code review.

Slices are not arrays. They are views.

A slice is a tiny struct with three fields: a pointer to an underlying array, a length, and a capacity.

go
nums := []int{10, 20, 30}
// pointer -> [10, 20, 30]
// len = 3, cap = 3

That is it. The slice itself is cheap to pass around. The data lives in the array it points to.

Diagram
rendering diagram...

make, append, and the reallocation rule

Two ways to create a slice: a literal, or make.

go
a := []string{"upi", "card", "wallet"} // literal, len=3, cap=3
b := make([]string, 0, 10)             // empty, len=0, cap=10
c := make([]int, 5)                    // five zeros, len=5, cap=5

Use make([]T, 0, n) when you know roughly how many elements you will append. It avoids reallocation. Small win, real latency saved when you process 50,000 orders an hour.

When append runs out of capacity, Go allocates a new underlying array (usually 2x the old cap), copies the old data, and your slice now points at the new array. The old array might still be alive because some other slice points at it.

Interview trap: appending to a slice you do not own can change someone else's data. If there is leftover capacity, append writes into the existing array. If a parent slice is still looking at that array, it sees the change.

go
all := []int{1, 2, 3, 4, 5}
first := all[:3]            // [1, 2, 3], shares backing array with all
first = append(first, 99)   // overwrites all[3]
// all is now [1, 2, 3, 99, 5]

Senior rule: if you receive a slice and plan to modify it, either document that loudly or copy it first with append([]T{}, src...).

nil slices are valid

You do not need to initialise a slice before using it. The zero value is nil, and a nil slice plays nice. You can call len() on it (returns 0), you can range over it (ranges zero times), and you can append to it (Go allocates the first time). The only thing you cannot do is index into one: orders[0] on a nil slice panics, the same way any out-of-bounds access does.

This is part of the same "zero value should be useful" principle from the structs lesson. A nil slice is the natural starting point for an accumulator, a builder pattern, or a function that returns a list that might be empty. You do not need a constructor, you do not need a sentinel "empty slice" variable, you just declare and go.

Maps, the daily driver

Maps are hash tables. Declare with make or a literal.

go
prices := map[string]int{
    "samosa": 20,
    "chai":   15,
}
prices["vada"] = 25
 
p, ok := prices["dosa"]
if !ok {
    fmt.Println("not on the menu")
}

The value, ok := m[key] form is called the comma-ok idiom. ok is true if the key exists, false otherwise. If you just write p := prices["dosa"], you get the zero value for the missing key, which can hide bugs.

Senior rule: when "absent" and "zero" mean different things, always use the comma-ok form.

Nil maps, delete, and iteration order

A nil map can be read from. It returns the zero value. But writing to it panics.

go
var menu map[string]int
_ = menu["chai"]             // returns 0, no panic
menu["chai"] = 15            // PANIC: assignment to entry in nil map
menu = make(map[string]int)  // safe now
delete(menu, "chai")         // no-op if absent, no error

This is the single most common Go panic in production for first-week Gophers. Initialise your maps before writing.

Iteration order is randomised on purpose. If you need sorted keys, collect them in a slice, sort the slice, then iterate. Maps also shrink slowly: if you delete a million keys, the underlying buckets are still allocated. For long-lived caches that churn a lot of keys, rebuild into a fresh map periodically.

Quick reference

Slice header
The three-field value (ptr, len, cap) you actually pass around. Cheap to copy.
Capacity
How many elements fit in the backing array before append must reallocate.
Backing array
The actual memory the slice points into. Multiple slices can share one.
Comma-ok
The v, ok := m[k] form for maps and type assertions. Tells you if the read found something.
Nil map panic
Writing to a map that was never made. Always make your maps.
Reallocation
When append outgrows the capacity and Go copies to a bigger backing array.

Next lesson, structs and methods. Where Go does OOP without ever saying the word.

Chai0/2 done

Watching quietly. Tap me if you want a tip.

Go Playground

Go cannot run natively in a browser. Run copies your code and opens go.dev/play ; paste and click Run there.

Try this (0 of 2 done)

  1. 1

    Predict the first part of the output (the slice contents).

    show answer
    package main
    
    import "fmt"
    
    func main() {
    	s := []int{1, 2, 3}
    	s = append(s, 4)
    	fmt.Println(s)
    }
  2. 2

    What does `m := map[string]int{"a":1}; v, ok := m["b"]; fmt.Println(v, ok)` print?

    hint

    Missing keys return the zero value of V plus ok=false.

    show answer
    m := map[string]int{"a": 1}
    v, ok := m["b"]
    fmt.Println(v, ok)