Go

Go Slices and Maps👨‍💻

Slices and maps are Go's primary collection types and understanding their internals is essential for writing correct, performant code. A slice is a three-word struct: a pointer to a backing array, a length, and a capacity. Appending beyond capacity allocates a new, larger array and copies the data. This means slices are reference-like (two slices can share a backing array) but assignments and function arguments copy the header, not the data — so append inside a function won't be visible to the caller unless you return the new slice.

Maps are hash tables with randomized iteration order. They're reference types — passing a map to a function lets that function modify the original. The zero value of a map is nil, and reading from a nil map is safe (returns the zero value), but writing to one panics. The comma-ok idiom (val, ok := m[key]) distinguishes between a missing key and a key whose value is the zero value.

Go 1.21 introduced the slices and maps packages in the standard library, providing generic functions for sorting, searching, comparing, cloning, and transforming slices and maps. These replace a large number of hand-written loops and the older sort.Slice patterns. Go 1.22 added range over int and Go 1.23 added iterator functions — both of which work naturally with slices and maps.

Key Takeaways

  • 1A slice header is three words: pointer to backing array, length (len), and capacity (cap) — understanding this explains every slice behavior
  • 2append may or may not allocate a new backing array: if cap is sufficient, it appends in-place; otherwise it allocates, copies, and returns a new slice header
  • 3Slicing (`s[lo:hi]`) creates a new slice header pointing into the same backing array — mutations are visible through both slices until a reallocation occurs
  • 4Maps are reference types with nil zero value: reading from nil is safe, writing panics — always initialize with make() or a literal before writing
  • 5The comma-ok idiom `val, ok := m[key]` is the only reliable way to distinguish a missing key from a zero-value entry
  • 6The `slices` and `maps` packages (Go 1.21+) provide generic Sort, Contains, Compact, Clone, Equal, and more — prefer them over hand-written loops

Master go slices and maps

Take the Go Programming course with hands-on lessons and challenges.

Examples

Slice internals: length, capacity, and shared backing arrays

go

This demonstrates how slices share backing arrays. The full slice expression s[lo:hi:max] limits capacity, forcing append to allocate a new array. Use it when you need to hand off a sub-slice without risking accidental mutation of the original data.

Append growth behavior and pre-allocation with make

go

Pre-allocating with make([]T, 0, n) avoids repeated allocations and copies. When the exact count is known, use make([]T, n) and index directly. This is one of the most common Go performance optimizations and is flagged by linters like prealloc.

Slice operations using the slices package (Go 1.21+)

go

The slices package replaces most hand-written slice manipulation loops. Sort, Compact, Contains, BinarySearch, Delete, and Clone cover the majority of real-world needs. Always use slices.Clone when you need an independent copy to avoid shared backing array issues.

Maps: creation, iteration, comma-ok, and the maps package

go

Maps are hash tables with randomized iteration order. The comma-ok idiom is essential for distinguishing absent keys from zero values. The maps package (Go 1.21+) provides Clone, Equal, Keys, and Values. For sorted iteration, use slices.Sorted(maps.Keys(m)).

Nil slices vs empty slices and their behavior

go

Nil and empty slices are functionally identical for len, cap, range, and append. The critical difference is JSON serialization: nil becomes null, empty becomes []. API responses almost always need [], so initialize with make([]T, 0) or a literal. Nil maps are safe to read but panic on write.

Common slice patterns: filter, deduplicate, chunk

go

Generic helper functions for common slice operations. filter uses slices.Clone + DeleteFunc to avoid mutating the input. deduplicate uses a map[E]struct{} set for O(1) lookups. chunk uses progressive slicing. The min builtin (Go 1.21+) replaces the manual min helper.

Common Mistakes

Mistake:

Appending to a slice inside a function and expecting the caller to see the new elements without returning the slice

Fix:

append may allocate a new backing array, which means the caller's slice header (pointer, len, cap) is stale. Always return the result of append: `s = append(s, v)`. If a function grows a slice, it must return the new slice or accept a pointer to the slice header.

Mistake:

Iterating over a map and assuming a stable order — then being surprised when tests fail intermittently

Fix:

Go randomizes map iteration order deliberately. If you need deterministic order, collect the keys with maps.Keys(), sort them with slices.Sort(), and iterate over the sorted keys. Never rely on insertion order.

Mistake:

Writing to a nil map, which causes a runtime panic

Fix:

The zero value of a map is nil. Reading is safe (returns the zero value), but writing panics. Always initialize maps with make(map[K]V) or a map literal before writing. A common pattern is lazy initialization: if m == nil { m = make(map[K]V) }.

Mistake:

Creating sub-slices that unintentionally keep the entire backing array alive, preventing garbage collection of a large underlying array

Fix:

If you only need a small portion of a large slice, use slices.Clone() or copy() to create an independent slice. This lets the garbage collector reclaim the large backing array. This matters when reading large files and keeping a small subset of rows.

Best Practices

  • Pre-allocate slices with make([]T, 0, n) when the approximate size is known — this avoids repeated allocations and is one of the easiest Go performance wins
  • Use the slices and maps packages (Go 1.21+) instead of hand-written loops for Sort, Contains, Compact, Clone, Equal, and Keys/Values
  • Use the full slice expression s[lo:hi:max] when handing off sub-slices to prevent accidental overwrites of shared backing array elements
  • Initialize response slices as make([]T, 0) instead of var s []T when the slice will be serialized to JSON, to produce [] instead of null
  • Prefer struct{} as the map value type for sets — map[string]struct{} uses zero bytes per entry for the value, compared to one byte for map[string]bool

Summary

Slices are three-word headers (pointer, length, capacity) backed by arrays, and understanding this explains append behavior, shared mutations, and capacity management. Maps are reference-type hash tables with randomized iteration order, where the comma-ok idiom is essential for safe key lookups. Go 1.21+ introduced the slices and maps packages, providing generic functions that replace most hand-written collection manipulation code. Pre-allocate when sizes are known, clone when independence is needed, and always return the result of append.

Practice Go with hands-on challenges

Learn go slices and maps hands-on in your IDE

Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.

Related Concepts

Related Cheatsheets

Master Go with Stanza

Interactive lessons and challenges, right in your code editor.

Check the free courses. No credit card.