Slices share memory, maps start out nil, and append sometimes copies. This guide explains the picture behind each one so the surprises stop being surprising.

Go keeps its language small, but slices and maps still trip up newcomers. The code compiles, the logic looks fine, and then one list changes when you did not touch it, or a program panics on a map you thought was ready.

Both have a simple mental picture behind them. Learn the picture and the surprises make sense.

By the end you will be able to:

  • Describe what a slice really is and why two slices can share data.
  • Explain len, cap and what append does.
  • Create, read, update and delete map entries safely.
  • Avoid the three classic mistakes with each.

Arrays are fixed, slices are flexible

An array in Go has a fixed length that is part of its type. [3]int and [4]int are different types. That is rarely what you want, so in everyday Go you use slices.

A slice does not hold the data itself. It is a small description of a window onto an array: where the window starts, how many items are in it (len), and how far it could stretch (cap).

The slice s[1:4] is a window over the same array. It does not copy anything.
slice.go
nums := []int{10, 20, 30, 40, 50}
s := nums[1:4]

fmt.Println(s)      // [20 30 40]
fmt.Println(len(s)) // 3
fmt.Println(cap(s)) // 4 (from index 1 to the end of the array)

The first surprise: slices share memory

Because s is only a window, changing it changes the original too:

shared.go
s[0] = 999
fmt.Println(nums) // [10 999 30 40 50]

This is not a bug. It is how slices stay cheap to pass around. When you want a truly independent copy, ask for one:

copy.go
clone := make([]int, len(s))
copy(clone, s)
clone[0] = 1 // nums is untouched

What append does

append adds items to the end. If the underlying array has spare room (cap is bigger than len) it writes into that room. If not, Go allocates a bigger array, copies everything across, and gives you a slice pointing at the new one.

Append is cheap while there is room, and does a copy when the array is full.

Maps: look things up by key

A map stores key and value pairs and finds a value quickly from its key. The type reads as map[KeyType]ValueType.

map.go
ages := map[string]int{
    "asha": 31,
    "ravi": 28,
}

ages["meera"] = 35        // add or update
delete(ages, "ravi")      // remove

age, ok := ages["asha"]   // the "comma ok" check
if ok {
    fmt.Println("Asha is", age)
}

The ok value matters. Reading a missing key does not fail. It quietly gives you the zero value (0 for an int), which looks exactly like a real zero. Use the comma-ok form whenever “missing” and “zero” mean different things.

range.go
for name, age := range ages {
    fmt.Println(name, age)
}
// Order is not guaranteed and can change between runs.

Three map mistakes to avoid

  1. Writing to a nil map. var m map[string]int gives you a nil map. Reading is fine, but writing panics. Create it with make(map[string]int) or a literal first.
  2. Expecting a fixed order. Go deliberately randomizes iteration order. Sort the keys if you need a stable order.
  3. Sharing a map between goroutines. Concurrent writes crash the program. Protect the map with a sync.Mutex, or use sync.Map for special cases.
A nil map is a map that was never created. Create it first and writes work.

Quick reference

TaskSliceMap
Createmake([]int, 0) or []int{1, 2}make(map[string]int) or a literal
Adds = append(s, x)m[key] = value
RemoveRe-slice, or use the slices packagedelete(m, key)
Sizelen(s)len(m)
Ordered?YesNo