Go Slices and Maps: Deep Dive
Master Go slices and maps — internal structure, performance, common patterns, and gotchas to avoid.
Slices
Slices are Go's most important data structure — a dynamic, flexible view into an underlying array.
Slice Internals
A slice has three components: pointer, length, and capacity.
``go
s := make([]int, 3, 5)
// len(s) = 3, cap(s) = 5
// s points to an array of size 5, but only 3 elements are accessible
`
Creating Slices
`go
// Literal
nums := []int{1, 2, 3, 4, 5}
// make zeros := make([]int, 10) // 10 zero-valued ints buffer := make([]int, 0, 100) // len=0, cap=100
// From array
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // [2, 3, 4]
`
Common Operations
`go
// Append
s := []int{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]
// Copy src := []int{1, 2, 3} dst := make([]int, len(src)) copy(dst, src)
// Delete element at index i s = append(s[:i], s[i+1:]...)
// Filter (no allocation if same backing array)
func filter(s []int, fn func(int) bool) []int {
result := s[:0] // Reuse backing array
for _, v := range s {
if fn(v) {
result = append(result, v)
}
}
return result
}
`
Slice Gotchas
`go
// Gotcha 1: Shared backing array
a := []int{1, 2, 3, 4, 5}
b := a[1:3] // [2, 3] — shares memory with a!
b[0] = 99
fmt.Println(a) // [1, 99, 3, 4, 5] — a is modified!
// Fix: Use full slice expression or copy b := append([]int{}, a[1:3]...)
// Gotcha 2: Append may or may not create new array
s := make([]int, 3, 3) // len == cap
s = append(s, 4) // New array allocated
`
Maps
Maps are Go's hash table implementation — unordered key-value collections.
Creating and Using Maps
`go
// Literal
ages := map[string]int{
"Alice": 28,
"Bob": 32,
}
// make scores := make(map[string]int) scores["math"] = 95
// Check existence val, ok := ages["Charlie"] if !ok { fmt.Println("Charlie not found") }
// Delete delete(ages, "Alice")
// Iterate (order is random!)
for key, value := range ages {
fmt.Printf("%s: %d\n", key, value)
}
`
Map Patterns
`go
// Set (using map[T]struct{})
seen := make(map[string]struct{})
seen["apple"] = struct{}{}
if _, ok := seen["apple"]; ok {
fmt.Println("Already seen")
}
// Counting counter := make(map[string]int) for _, word := range words { counter[word]++ }
// Grouping
groups := make(map[string][]User)
for _, user := range users {
groups[user.Role] = append(groups[user.Role], user)
}
`
Map Safety
Maps are not safe for concurrent use. Use sync.Map or sync.Mutex:
`go
var mu sync.RWMutex
var data = make(map[string]string)
func get(key string) string { mu.RLock() defer mu.RUnlock() return data[key] }
func set(key, value string) {
mu.Lock()
defer mu.Unlock()
data[key] = value
}
`
Performance Tips
1. Pre-allocate slices and maps when you know the size: make([]T, 0, n)
2. Use copy to avoid shared backing array issues
3. For large maps, consider sync.Map for concurrent read-heavy workloads
4. Slices of structs are more cache-friendly than slices of pointers
5. Use clear()` (Go 1.21+) to reset slices and maps