Maps
Maps are Go's hash table — unordered key-value collections.
Introduction
Maps are Go's hash table — unordered key-value collections. Keys must be comparable (no slices, maps, or funcs as keys). Maps are reference types: passing a map to a function allows the callee to mutate the same underlying data.
Maps are not safe for concurrent access without sync.Mutex or sync.Map. The comma-ok idiom distinguishes missing keys from zero values. Understanding nil map behavior (reads OK, writes panic) prevents production crashes.
Maps appear in config parsing, in-memory caches, HTTP header lookups, and JSON object deserialization — this lesson covers creation, iteration, deletion, and concurrency patterns.
The story
An API gateway caches JWT validation results keyed by token fingerprint — a map[string]Claims gives O(1) lookups under high request rates. At Uber, in-memory maps back rate-limit counters per rider ID, while Redis maps extend the same pattern across distributed edge nodes.
Maps are reference types: passing them to functions shares the same underlying hash table, so concurrent access requires synchronization (mutex or sync.Map) in production servers.
Understanding the topic
Key concepts
- m := make(map[string]int) or map[string]int{} — latter creates empty non-nil map.
- var m map[string]int is nil — read returns zero, write panics.
- Comma-ok: val, ok := m[key]; delete(m, key) removes entry.
- Iteration order is randomized — don't depend on order.
- Maps not concurrent-safe — use sync.Map or mutex wrapper.
- maps package (Go 1.21+): Keys, Values, Clone, Equal helpers.
Step-by-step explanation
- make allocates internal hash table; literal {} also initializes.
- Insert/update: m[key] = value.
- Lookup: val := m[key] — zero value if missing.
- delete removes key; no error if key absent.
- len(m) is element count; no cap concept.
- Range over map gives key, value in random order.
Practical code example
In-memory rate limiter map with comma-ok and safe initialization:
package mainimport ("fmt""maps")type RateLimiter struct {counts map[string]intlimit int}func NewRateLimiter(limit int) *RateLimiter {return &RateLimiter{counts: make(map[string]int), limit: limit}}func (r *RateLimiter) Allow(clientID string) bool {count, _ := r.counts[clientID]if count >= r.limit {return false}r.counts[clientID] = count + 1return true}func main() {rl := NewRateLimiter(2)fmt.Println(rl.Allow("client-a"), rl.Allow("client-a"), rl.Allow("client-a"))snapshot := maps.Clone(rl.counts)fmt.Println("snapshot:", snapshot)}
Line-by-line code explanation
cache := make(map[string]Claims)creates an empty map ready for insertion.cache[token] = claimsstores a value — creates the key if absent.claims, ok := cache[token]reads with a boolean indicating whether the key existed.if !ok { ... }handles cache misses without confusing zero values for real data.delete(cache, token)removes an entry — safe even if the key is missing.for k, v := range cacheiterates entries — order is intentionally randomized.len(cache)returns the number of key-value pairs currently stored.var m map[string]intnil map reads return zero but writes panic — always initialize withmake.
Key takeaway: Always make() or literal before writes. Clone map before exposing internal state. For concurrent use, wrap with sync.Mutex.
Real-world use
Where you'll use this in production
- In-memory caches with TTL wrappers (sync.Map or ristretto).
- HTTP middleware rate limiting per client IP.
- JSON object decoding into map[string]any for dynamic payloads.
- Graph adjacency lists and lookup tables in algorithms.
Best practices
- Initialize with make or {} before any write.
- Use comma-ok for existence checks, not zero-value comparison.
- Protect concurrent maps with mutex or sync.Map.
- Clone before returning internal map from struct methods.
- Use struct{} as value for set semantics: map[string]struct{}.
- Consider sync.Map for read-heavy concurrent caches.
Common mistakes
- Writing to nil map — panic.
- Ranging and deleting same map — safe in Go but be deliberate.
- Using float64 as map key — NaN != NaN breaks lookups.
- Assuming iteration order for tests — flaky tests result.
- Returning internal map without copy — caller can mutate state.
Advanced interview questions
Q1BeginnerCan you use slice as map key?
Q2Beginnernil map read vs write?
Q3IntermediateMap iteration order?
Q4Intermediatesync.Map vs map+Mutex?
Q5AdvancedImplement thread-safe cache with TTL in Go.
Summary
Maps are reference-type hash tables; initialize before writing. Comma-ok distinguishes missing keys from zero values. Not concurrent-safe — use sync.Mutex or sync.Map. Iteration order is random — by design. Next lesson: structs — grouping related data.