Skip to content

Table of Contents

Slices, Maps & Arrays

Contents

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.

t[0] = 999             // s[1] is now 999 too

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:

m := map[string]*Point{}
m["a"] = &Point{}
m["a"].X = 5                  // works because value is a pointer

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