>_
GolangStepByStep
Software Engineer

Arrays, Slices & Maps

Master Go's core collection types and their internals

# Start Here: One Simple Mental Model

Arrays, slices, and maps are related, but they solve different problems:

  • Array = actual fixed-size storage
  • Slice = flexible view over storage
  • Map = key-based lookup table

Beginners should focus on when to use each. Senior engineers should focus on memory, ownership, mutation, iteration behavior, and edge cases.

# Arrays — Fixed Size and Value Semantics

Arrays have fixed size, and the size is part of the type. This means [3]int and [4]int are different types.

var a [3]int
b := [3]int{10, 20, 30}
c := [...]int{1, 2, 3, 4}

fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(len(c))

Arrays are less common in everyday service code, but they appear in hashes, protocol formats, fixed-size tables, and places where value semantics are useful.

# Slices — The Most Important Collection in Go

Slices are what you use most of the time. They are not the data itself. They are a descriptor of where data starts, how much is visible, and how much spare room exists.

s1 := []int{1, 2, 3}
s2 := make([]int, 5)
s3 := make([]int, 3, 10)

fmt.Println(s1)
fmt.Println(len(s2), cap(s2))
fmt.Println(len(s3), cap(s3))
▸ Easy analogy: a slice is a window, not a new room
Imagine an array is a long wall of sticky notes. A slice is just a window that shows part of the wall. If two windows look at the same wall, changes through one window are visible through the other.

# The Sharing Gotcha Everyone Must Understand

Slices usually share backing arrays. This is the most important slice concept for interviews and real-world debugging.

a := []int{1, 2, 3, 4, 5}
b := a[1:3]
b[0] = 99

fmt.Println(a) // [1 99 3 4 5]
fmt.Println(b) // [99 3]

b starts at a[1], so writing to b[0] changes a[1] too. If you need independence, copy the slice.

# Append, Capacity, and Reallocation

append may reuse the same backing array or allocate a new one. That depends on capacity.

s := []int{1, 2, 3}
s = append(s, 4, 5)
fmt.Println(s)

Always capture the returned value. append is allowed to return a new slice header pointing at a new backing array.

# Nil Slice vs Empty Slice

var a []int
b := []int{}

fmt.Println(a == nil) // true
fmt.Println(b == nil) // false
fmt.Println(len(a), len(b)) // 0 0

Both often behave similarly, but they are not identical. This matters for APIs, marshaling, and edge-case correctness.

# Copying Slices Safely

If you need to prevent shared mutation, make a copy.

src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)

dst[0] = 999
fmt.Println(src)
fmt.Println(dst)

# Maps — Lookup, Grouping, Counting

Maps are the right tool for fast lookup by key, counting, deduplication, indexing, and grouping.

ages := map[string]int{
    "Alice": 30,
    "Bob":   25,
}

ages["Carol"] = 28
delete(ages, "Bob")

age, ok := ages["Dave"]
fmt.Println(age, ok)

Use the two-value form when zero values are meaningful. For example, 0 may mean either “missing” or “present and zero”.

# Nil Map vs Empty Map

Reading from a nil map is okay. Writing to a nil map panics.

var m map[string]int
fmt.Println(m["x"]) // 0

m = make(map[string]int)
m["x"] = 1
fmt.Println(m["x"]) // 1

# Most Asked Practical Scenarios

Frequency counting

words := []string{"go", "is", "go", "fun", "go"}
freq := make(map[string]int)
for _, w := range words {
    freq[w]++
}

Deduplicate a slice

seen := make(map[int]bool)
out := make([]int, 0, len(nums))
for _, n := range nums {
    if !seen[n] {
        seen[n] = true
        out = append(out, n)
    }
}

Delete by index from a slice

s = append(s[:i], s[i+1:]...)

In-place filtering

func filterEvens(nums []int) []int {
    out := nums[:0]
    for _, n := range nums {
        if n%2 == 0 {
            out = append(out, n)
        }
    }
    return out
}

# Edge Cases Senior Engineers Care About

  • Map iteration order is unspecified — sort keys when order matters.
  • Sub-slicing large buffers can retain large backing arrays unexpectedly.
  • Use three-index slicing when you want to limit shared capacity.
  • Arrays can be map keys; slices cannot.
  • Maps are not safe for concurrent writes without synchronization.
  • Nil vs empty slices can affect JSON and public API semantics.

⚡ Key Takeaways

  • Arrays are fixed-size value types.
  • Slices are views over arrays, so sharing and capacity matter.
  • Always reassign the result of append.
  • Use v, ok := m[key] when key presence matters.
  • Never write to a nil map.
  • Copy slices when you need ownership isolation.
practice & review