Table of Contents
- Slices, Maps & Arrays
- Contents
- Arrays
- Slice internals
- Creating slices
- append behavior
- copy
- Slice tricks
- Maps
- Ordered iteration
- Gotchas
Slices, Maps & Arrays¶
Contents¶
- Arrays
- Slice internals
- Creating slices
- append behavior
- copy
- Slice tricks
- Maps
- Ordered iteration
- Gotchas
Arrays¶
Fixed length, length is part of the type, value semantics (copied on assignment/pass).
var a [3]int // [0 0 0]
b := [3]int{1, 2, 3}
c := [...]int{1, 2, 3} // compiler counts -> [3]int
d := [5]int{2: 7} // index init -> [0 0 7 0 0]
len(a) // 3 (constant)
a == b // arrays are comparable if elements are
e := b // FULL COPY
e[0] = 99 // b unchanged
Arrays are rarely used directly; slices are the workhorse.
Slice internals¶
A slice is a 3-word header: {ptr *T, len int, cap int} pointing into a backing array.
s := []int{10, 20, 30, 40, 50}
// ^ptr len=5 cap=5
t := s[1:3] // {ptr=&s[1], len=2, cap=4} -> [20 30]
len(t) // 2 cap(t) // 4 (from index 1 to end of backing array)
// Full slice expression caps the result: s[low:high:max]
u := s[1:3:3] // len=2, cap=2 (max-low)
Slices share the backing array; mutating through one slice is visible through others that overlap.
Creating slices¶
var s []int // nil slice (len 0, cap 0) — usable
s = append(s, 1) // append to nil is fine
m := make([]int, 3) // [0 0 0], len 3 cap 3
n := make([]int, 0, 10) // len 0, cap 10 (preallocate to avoid regrowth)
lit := []string{"a", "b"}
grid := [][]int{{1, 2}, {3, 4}} // slice of slices
nil vs empty: both have len 0; s == nil is true only for nil. Prefer len(s) == 0 for emptiness checks. JSON marshals nil slice as null, empty slice as [].
append behavior¶
append returns a (possibly new) slice — always reassign.
s = append(s, 1)
s = append(s, 2, 3, 4) // multiple
s = append(s, other...) // spread another slice
b = append(b, "str"...) // append string bytes to []byte
Growth: if cap is exceeded, Go allocates a NEW backing array (roughly doubling for small slices), copies elements, and the old array is no longer shared.
a := []int{1, 2, 3}
b := a[:2] // shares backing array, cap 3
b = append(b, 99) // FITS in cap -> overwrites a[2]!
// a == [1 2 99]
// vs. when cap is exceeded:
c := []int{1, 2, 3}
d := c[:3] // cap 3, full
d = append(d, 4) // reallocates; c untouched
This aliasing is the #1 slice bug. Use the 3-index slice or copy to force isolation.
copy¶
dst := make([]int, len(src))
n := copy(dst, src) // copies min(len(dst), len(src)); returns n
// Clone idiom (Go 1.21+):
clone := slices.Clone(src) // shallow copy into a fresh backing array
// Pre-1.21 clone:
clone = append([]int(nil), src...)
copy works between overlapping slices and from string to []byte.
Slice tricks¶
import "slices" // Go 1.21+ for many of these
// Delete index i (order-preserving), O(n)
s = append(s[:i], s[i+1:]...)
s = slices.Delete(s, i, i+1) // stdlib equivalent
// Delete index i (fast, reorders): swap with last
s[i] = s[len(s)-1]
s = s[:len(s)-1]
// Insert value at index i
s = append(s[:i], append([]int{v}, s[i:]...)...)
s = slices.Insert(s, i, v) // stdlib
// Filter in place (no allocation): reuse backing array
keep := s[:0]
for _, x := range s {
if predicate(x) {
keep = append(keep, x)
}
}
s = keep
// Reverse, sort, search (Go 1.21+)
slices.Reverse(s)
slices.Sort(s)
slices.SortFunc(people, func(a, b Person) int { return cmp.Compare(a.Age, b.Age) })
i, found := slices.BinarySearch(s, 42)
slices.Contains(s, 42)
slices.Index(s, 42)
slices.Equal(a, b)
slices.Max(s); slices.Min(s)
// Stack
stack = append(stack, x) // push
x, stack = stack[len(stack)-1], stack[:len(stack)-1] // pop
// Dedup adjacent (sorted) duplicates
s = slices.Compact(s)
Maps¶
var m map[string]int // nil map: reads ok, writes PANIC
m = make(map[string]int) // ready to use
m = map[string]int{"a": 1} // literal
m = make(map[string]int, 100) // preallocate hint
m["k"] = 7 // set
v := m["missing"] // 0 (zero value), no error
v, ok := m["k"] // comma-ok: ok=false if absent
delete(m, "k") // remove (no-op if absent)
len(m) // size
clear(m) // remove all entries (Go 1.21+)
// Set pattern: map[T]struct{} (zero-byte value)
set := map[string]struct{}{}
set["x"] = struct{}{}
_, exists := set["x"]
// Map of slices: append works on missing key (nil slice)
groups := map[string][]int{}
groups["a"] = append(groups["a"], 1)
Key types must be comparable (no slices, maps, funcs). Structs/arrays of comparable types work as keys.
You cannot take the address of a map element (&m["k"] is illegal). To mutate a struct value in a map, replace the whole value or store pointers:
Ordered iteration¶
Map iteration order is randomized by design. To iterate in order, sort the keys:
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
slices.Sort(keys) // or sort.Strings(keys)
for _, k := range keys {
fmt.Println(k, m[k])
}
// Go 1.23+: keys/values iterators
for k := range maps.Keys(m) {} // returns an iter.Seq
Gotchas¶
// 1. Writing to a nil map panics
var m map[string]int
m["x"] = 1 // panic: assignment to entry in nil map
// 2. Append aliasing (see append section) — reallocation surprises.
// 3. Range copies the value; mutating v doesn't change the slice
for _, v := range s { v.Field = 1 } // no effect on s
for i := range s { s[i].Field = 1 } // correct
// 4. Range expression evaluated once; appending in-loop won't extend it
for i := range s { s = append(s, 0) } // loops len(s)-original times
// 5. Large backing array kept alive by a small sub-slice (memory leak)
small := bigSlice[:2] // still pins all of bigSlice
small = slices.Clone(bigSlice[:2]) // detach to free the rest
// 6. Comparing slices/maps with == is a compile error (except to nil)
// Use slices.Equal / maps.Equal / reflect.DeepEqual
// 7. Per-iteration loop var (Go 1.22+): closures capture a fresh var each
// iteration. Pre-1.22 you had to copy: v := v