Arrays, Slices, and Maps
Arrays
Section titled “Arrays”Arrays are fixed-length, homogeneous sequences. The length is part of the type — [3]int and [5]int are different types.
var a [3]inta[0] = 1a[1] = 2a[2] = 3
b := [3]int{1, 2, 3}c := [...]int{1, 2, 3} // compiler infers lengthArrays are values. Assigning or passing an array copies the entire array:
a := [3]int{1, 2, 3}b := ab[0] = 99fmt.Println(a) // [1 2 3] -- unchangedfmt.Println(b) // [99 2 3]Arrays are stack-allocated when they are local variables. Large arrays (> a few kilobytes) may be Better represented as slices to avoid stack frame bloat.
Array Length
Section titled “Array Length”The built-in len returns the length:
a := [5]int{1, 2, 3, 4, 5}fmt.Println(len(a)) // 5Slices
Section titled “Slices”Slices are dynamically-sized, flexible views into arrays. A slice is a descriptor containing a Pointer to an underlying array, a length, and a capacity:
+-------------------+| ptr | len | cap |+--+---+-----+------+ | v+---+---+---+---+---+---+| 1 | 2 | 3 | | | |+---+---+---+---+---+---++---len---++-------cap--------+Creating Slices
Section titled “Creating Slices”// Literals := []int{1, 2, 3}
// make with length and capacitys := make([]int, 5) // len=5, cap=5s := make([]int, 0, 10) // len=0, cap=10
// From an arraya := [5]int{1, 2, 3, 4, 5}s := a[1:3] // [2, 3], len=2, cap=4Slicing
Section titled “Slicing”Slicing creates a new slice that references the same underlying array:
a := []int{0, 1, 2, 3, 4, 5}s1 := a[1:4] // [1, 2, 3], len=3, cap=5s2 := a[2:] // [2, 3, 4, 5], len=4, cap=4s3 := a[:3] // [0, 1, 2], len=3, cap=6s4 := a[:] // [0, 1, 2, 3, 4, 5], len=6, cap=6Modifying a slice modifies the underlying array, which is visible through all slices sharing it:
a := []int{0, 1, 2, 3}s := a[1:3]s[0] = 99fmt.Println(a) // [0 99 2 3]Slice Operations
Section titled “Slice Operations”Append
Section titled “Append”append adds elements to a slice. If the capacity is exceeded, a new underlying array is allocated And all elements are copied:
s := []int{1, 2}s = append(s, 3) // [1, 2, 3]s = append(s, 4, 5) // [1, 2, 3, 4, 5]s = append(s, []int{6, 7}...) // [1, 2, 3, 4, 5, 6, 7]append may or may not allocate a new array. The return value must be captured:
s := make([]int, 0, 2)s = append(s, 1) // len=1, cap=2 -- no reallocations = append(s, 2) // len=2, cap=2 -- no reallocations = append(s, 3) // len=3, cap=4 -- reallocation occurredcopy copies elements from a source slice to a destination slice:
src := []int{1, 2, 3}dst := make([]int, len(src))n := copy(dst, src)fmt.Println(dst) // [1, 2, 3]fmt.Println(n) // 3 -- number of elements copiedcopy copies min(len(dst), len(src)) elements.
Since Go 1.21, clear zeros the elements of a slice:
s := []int{1, 2, 3}clear(s)fmt.Println(s) // [0, 0 0]Slice Growth Strategy
Section titled “Slice Growth Strategy”When append triggers reallocation, Go doubles the capacity for slices smaller than 256 Elements and grows by ~25% for larger slices. The exact strategy is an implementation detail of the Runtime and should not be relied upon.
Pre-allocate capacity when the final size is known:
// Bad: causes multiple reallocationsvar s []intfor i := 0; i < 10000; i++ { s = append(s, i)}
// Good: single allocations := make([]int, 0, 10000)for i := 0; i < 10000; i++ { s = append(s, i)}Nil vs Empty Slices
Section titled “Nil vs Empty Slices”var nilSlice []int // nil, len=0, cap=0emptySlice := []int{} // not nil, len=0, cap=0madeSlice := make([]int, 0) // not nil, len=0, cap=0The difference matters for JSON marshaling: nil marshals to nullWhile []int{} marshals to []. For most other purposes, they are interchangeable.
Maps are hash tables mapping keys to values. The zero value is nil. A nil map is empty but Cannot be written to.
var m map[string]int // nil mapm = make(map[string]int)m["key"] = 42
// Literalm := map[string]int{ "a": 1, "b": 2, "c": 3,}Map Operations
Section titled “Map Operations”m := map[string]int{"x": 10, "y": 20}
// Readv := m["x"] // 10v := m["missing"] // 0 -- zero value for missing keys (no error)
// Read with existence checkv, ok := m["x"]if ok { fmt.Println("found:", v)}
// Deletedelete(m, "x")
// Lengthfmt.Println(len(m))
// Iterate (order is not guaranteed)for k, v := range m { fmt.Printf("%s: %d\n", k, v)}Map Keys
Section titled “Map Keys”Map keys must be comparable. Comparable types are: booleans, integers, floats, strings, pointers, Interfaces (if the dynamic type is comparable), structs (if all fields are comparable), and arrays (if element type is comparable).
Slices, maps, and functions are not comparable and cannot be used as map keys.
Map Capacity
Section titled “Map Capacity”Pre-allocate capacity when the approximate size is known:
m := make(map[string]int, 1000) // hint for ~1000 entriesStructs
Section titled “Structs”Structs are aggregate types that group named fields:
type Point struct { X, Y float64}
type Circle struct { Center Point Radius float64}Struct Literals
Section titled “Struct Literals”p := Point{1.0, 2.0}p := Point{X: 1.0, Y: 2.0} // named fields (order does not matter)p := Point{} // zero value: {0, 0}Anonymous Structs
Section titled “Anonymous Structs”Useful for intermediate data without defining a named type:
result := struct { Value int Err error}{ Value: 42, Err: nil,}Struct Embedding
Section titled “Struct Embedding”Go supports type embedding (not inheritance):
type Base struct { ID int Name string}
type Derived struct { Base Extra string}
d := Derived{Base: Base{ID: 1, Name: "test"}, Extra: "data"}fmt.Println(d.ID) // 1 -- promoted fieldfmt.Println(d.Name) // "test" -- promoted fieldPromoted fields are accessed directly on the embedding struct. This is syntactic sugar — there is No inheritance hierarchy. The embedded struct”s methods are also promoted.
Comparing Structs
Section titled “Comparing Structs”Structs are comparable if all their fields are comparable:
a := Point{1.0, 2.0}b := Point{1.0, 2.0}fmt.Println(a == b) // trueStructs containing slices or maps are not comparable.
Intuition
Section titled “Intuition”A slice is a window into a larger array: Imagine a long bookshelf (the underlying array). A slice is a bookmark and a page-count telling you which window of books you’re currently reading. Multiple slices can peer into the same shelf, so flipping a page in one view changes what another view sees. When you try to read past the window, Go gets a bigger shelf and copies everything over.
Why it matters: Understanding the slice-as-window mental model prevents the most common Go bugs: forgetting that append may reallocate, holding onto a tiny slice that keeps a huge array alive, or writing through one slice and surprising another.
The key insight: Slices are cheap because they’re just pointers with bounds — but that shared backing array means operations that look local can have global effects.
Common Pitfalls
Section titled “Common Pitfalls”Forgetting to capture
appendreturn value.appendmay allocate a new underlying array. The original slice header is not updated. Always writes = append(s, ...).Slicing retains the underlying array. A small slice of a large array prevents the large array from being garbage collected. Use
copyto create an independent copy:small := make([]byte, len(large[1000:1100]))copy(small, large[1000:1100])Writing to a
nilmap.var m map[string]intcreates a nil map. Writing to it panics. Always initialize withmakeor a literal before writing.Map iteration order is random. Go randomizes map iteration order. Do not rely on it. If you need ordered iteration, maintain a separate sorted key slice.
Concurrent map access. Maps are not safe for concurrent use. Use
sync.RWMutexorsync.Mapfor concurrent access.Struct embedding is not inheritance. There is no
supercall, no method overriding in the OOP sense. Embedding is composition with syntactic sugar for field/method promotion.
Summary
Section titled “Summary”This topic covers the core concepts of arrays, slices, and maps, including underlying theory, practical implementation, and key applications.
Key concepts include:
- core concepts and terminology
- algorithms and computational thinking
- practical implementation
- security and ethical considerations
- applications in the real world
Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.
Worked Examples
Section titled “Worked Examples”Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.
Cross-References
Section titled “Cross-References”- Types and Variables: Scalar types that compose into slices, maps, and arrays.
- I/O: Buffer and stream operations on byte slices and strings.
- Interfaces: How slices and maps implement standard library interfaces.