Arrays, Slices & Maps at a Glance
Arrays are fixed-size values; slices and maps are reference-like collections built into the language.
Most Go code uses slices and maps daily, so len, cap, and sharing rules matter.
Summary
An array [N]T has size part of the type.
A slice []T is a header (pointer, len, cap) over an underlying array.
A map map[K]V is a hash table with undefined iteration order.
Slices and maps are passed by copying the header, so mutations are visible to callers.
Recipe
Quick-reference recipe card - copy-paste ready.
package main
import "fmt"
func main() {
nums := make([]int, 0, 4)
nums = append(nums, 1, 2, 3)
ages := map[string]int{"ada": 36}
ages["grace"] = 34
fmt.Println(nums, len(nums), cap(nums), ages)
}When to reach for this:
- Use slices for ordered, growable sequences.
- Use maps for keyed lookup when keys are comparable.
- Use arrays only when size is a fixed protocol constraint (crypto, embedded).
- Preallocate with
make([]T, 0, n)when you know approximate size.
Working Example
package main
import "fmt"
func addSuffix(names []string, suffix string) {
for i := range names {
names[i] += suffix
}
}
func main() {
original := []string{"go", "rust"}
view := original[0:2:2] // full slice expression caps capacity
addSuffix(view, "!")
counts := map[string]int{}
for _, name := range original {
counts[name]++
}
fmt.Println(original, counts)
keys := make([]string, 0, len(counts))
for k := range counts {
keys = append(keys, k)
}
fmt.Println("keys (unordered):", keys)
}What this demonstrates:
- Slice headers copy but share backing arrays -
addSuffixmutatesoriginal. - Full slice expression
s[low:high:max]limits append growth capacity. - Maps need
makeor literals before writes. - Collecting map keys requires a separate slice; order is not stable.
Deep Dive
How It Works
- Slice
appendgrows capacity by allocating a new array whenlen == cap(typically doubling). - Slicing
s[low:high]sets len tohigh-lowand cap tocap(s)-low. - Map buckets are internal; iteration order is randomized each pass.
- Arrays assign by value (full copy); slices and maps assign headers (shared storage).
Arrays vs Slices vs Maps
Array [n]T | Slice []T | Map map[K]V | |
|---|---|---|---|
| Size | Fixed in type | Dynamic | Dynamic |
| Pass to func | Copy whole array | Copy header | Copy header |
| Zero value | All elements zero | nil | nil |
| Comparable | Yes (if T comparable) | No | No |
| Index access | a[i] | s[i] | m[k] with ok form |
len and cap
| Expression | Meaning |
|---|---|
len(s) | Number of elements visible in slice |
cap(s) | Elements from first index through end of backing array |
make([]T, len, cap) | Allocates array, sets len and cap |
append(s, x) | Grows len, may reallocate if len == cap |
Go Notes
// Delete map entry
delete(m, key)
// Clear map in Go 1.21+
clear(m)
// Copy slices
dst := make([]int, len(src))
copy(dst, src)Gotchas
- Append alias surprise -
a := []int{1,2,3}; b := a[:2]; append(b, 99)may overwritea[2]if cap allows. Fix: use full slicea[:2:2]orappendto a copy. - Nil map write - Assigning to uninitialized map panics. Fix:
makefirst. - Expecting sorted map iteration - Order changes between loops. Fix: sort keys slice before display.
- Using array as slice stand-in -
[5]intis not[]intwithout slicingarr[:]. Fix: slice with[:]. - Large array copies - Passing big arrays copies all elements. Fix: pass pointer
*[N]Tor use slice. - Concurrent map writes - Maps are not safe for concurrent write without sync. Fix:
sync.Mapor mutex.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Slice | Default ordered collection | Need O(1) lookup by key |
| Map | Keyed lookup | Need stable order without extra sort |
| Array | Fixed wire format size | General-purpose lists |
container/list | Frequent middle inserts (rare) | Most cases - slices win |
FAQs
What is the difference between len and cap?
len is how many elements you can read/write from index 0.
cap is how many elements exist in the backing array starting at the slice's first element.
Is a nil slice valid?
Yes - len and cap are 0, range works, and append allocates backing storage.
How do I check if a map has a key?
v, ok := m[key]
if !ok { /* missing */ }Can map keys be slices?
No - keys must be comparable; slices, maps, and functions are not comparable.
How do I preallocate a slice?
make([]T, 0, expected) then append, or make([]T, expected) if you will index directly.
What does s[low:high:max] do?
Sets len to high-low and cap to max-low, preventing append from reading past max.
How are arrays passed to functions?
The entire array copies.
For [1024]byte buffers, pass a slice buf[:] or pointer instead.
Can I take the address of a map element?
No - &m[key] is illegal because map storage can move during growth.
Store value in a variable first if you need a pointer.
How do I merge maps?
Loop and assign: for k, v := range other { m[k] = v }.
Watch for overwriting existing keys.
Does append return the same slice header?
It may return a new header if reallocation happens.
Always assign: s = append(s, x).
What is the zero value of [3]int vs []int?
Array is [0 0 0] value type.
Slice is nil header with no backing array.
How do I shallow-copy a slice?
append([]T(nil), s...) or s2 := make([]T, len(s)); copy(s2, s).
Related
- Zero Values and Initialization - nil maps and slices
- Pointers & the Address-of Operator - Sharing via slice headers
- Numeric Types, Strings, Runes & Byte Slices -
[]byteas byte slices - Structs, Embedding & Field Promotion - Structs containing slices and maps
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).