Go Collections
Purpose
Go provides three built-in collection types: arrays (fixed-size, rarely used directly), slices (dynamic views over arrays, the workhorse of ordered lists), and maps (key→value hash tables). Understanding slice and map internals prevents subtle sharing bugs.
Implementation Notes
Arrays
Fixed size, determined at compile time. Rarely used directly — slices are almost always preferred.
var myInts [10]int // zero-valued array
primes := [6]int{2, 3, 5, 7, 11, 13} // literalSlices
A slice is a dynamically-sized, flexible view of an underlying array. The zero value of a slice is nil.
Creating slices
// literal
s := []string{"I", "love", "go"}
// make([]T, len, cap) — filled with zero values
s := make([]int, 5) // len=5, cap=5
s := make([]int, 3, 8) // len=3, cap=8Slice expressions — lowIndex is inclusive, highIndex is exclusive:
primes := [6]int{2, 3, 5, 7, 11, 13}
s := primes[1:4] // [3 5 7]
s = primes[2:] // from index 2 to end
s = primes[:3] // first three elements
s = primes[:] // entire arrayLength and capacity
fmt.Println(len(s), cap(s))append — always assign back to the same variable:
s = append(s, 4)
s = append(s, 1, 2, 3)
s = append(s, otherSlice...) // variadic spreadIf the underlying array has insufficient capacity, append allocates a new one. This is safe when appending to the same variable; appending to a different variable from a shared base can cause aliasing (see Trade-offs).
Range iteration — the element is a copy:
fruits := []string{"apple", "banana", "grape"}
for i, fruit := range fruits {
fmt.Println(i, fruit)
}Slices of slices (2D):
rows := [][]int{}Variadic functions accept ...T and receive arguments as a slice:
func concat(strs ...string) string {
result := ""
for _, s := range strs {
result += s
}
return result
}
names := []string{"a", "b", "c"}
concat(names...) // spread a slice into variadic argsMaps
A map provides key→value mapping. The zero value is nil; a nil map panics on write.
Creating maps
// make
ages := make(map[string]int)
ages["John"] = 37
// literal
ages := map[string]int{
"John": 37,
"Mary": 21,
}Reading — missing keys return the zero value of the value type:
v := ages["unknown"] // 0, no panicComma-ok idiom — distinguish a missing key from a zero-value entry:
v, ok := ages["John"]
if !ok {
// key not present
}Mutations
ages["Alice"] = 30 // insert / update
delete(ages, "Alice") // delete (safe even if key absent)
fmt.Println(len(ages)) // number of key/value pairsNested maps
hits := make(map[string]map[string]int)
// or use a struct key to avoid inner-map initialisation:
type Key struct{ Path, Country string }
hits2 := make(map[Key]int)
hits2[Key{"/", "au"}]++Key type constraints — keys must be comparable (bool, numeric, string, pointer, channel, interface, or structs/arrays of those). Slices, maps, and functions cannot be map keys.
Trade-offs
Shared backing array (slices) — when two slices share the same underlying array and there is still spare capacity, an append to one will silently overwrite the other:
i := make([]int, 3, 8)
j := append(i, 4) // j and i share array; cap not yet exhausted
g := append(i, 5) // g overwrites index 3 — j[3] is now 5!Rule: always append to the same variable: s = append(s, v).
Maps are not goroutine-safe — concurrent read/write causes a runtime panic (fatal error: concurrent map iteration and map write). Protect with a sync.Mutex or sync.RWMutex. See go_concurrency.
Maps pass by reference — passing a map to a function mutates the caller’s map; no copy is made.
References
Links
- go_concurrency — mutex protection for maps
- go_generics — generic functions over slices
- go_index