Maps are Go's built-in hash table. When you ask AI to store key-value relationships, look something up by ID, or count occurrences, it will reach for a map. The syntax is straightforward, but maps have several sharp edges around nil values, concurrencyWhat is concurrency?The ability of a program to handle multiple tasks at the same time, like serving thousands of users without slowing down., and iteration order that AI handles poorly.
Creating and using maps
// Literal
ages := map[string]int{
"Alice": 30,
"Bob": 25,
}
// make()
scores := make(map[string]int)
scores["Alice"] = 95
scores["Bob"] = 87
// Reading
fmt.Println(ages["Alice"]) // 30
// Missing key returns zero value
fmt.Println(ages["Nobody"]) // 0| Map type | Use case |
|---|---|
map[string]int | Word counts, scores, frequencies |
map[int]string | ID-to-name lookups |
map[string][]string | Grouping (tags to items, roles to users) |
map[string]struct{} | Sets (the empty struct uses zero memory) |
map[string]map[string]int | Nested lookups (use sparingly) |
The comma-ok idiom
Accessing a missing key returns the zero value silently. You cannot tell the difference between "key exists with value 0" and "key doesn't exist" without the comma-ok pattern:
scores := map[string]int{"Alice": 95, "Bob": 0}
// Dangerous - is Bob's score 0 or does Bob not exist?
score := scores["Bob"] // 0
score2 := scores["Eve"] // also 0
// Safe - comma-ok tells you
score, ok := scores["Bob"]
if ok {
fmt.Println("Bob's score:", score) // 0 - he exists
}
if _, exists := scores["Eve"]; !exists {
fmt.Println("Eve not found")
}if m[key] != 0 to check for existence. This fails when zero is a valid value. Always use the comma-ok idiom: if v, ok := m[key]; ok { ... }. Train yourself to spot the missing ok in AI output.Nil maps vs empty maps
This is the single most common map-related panic in AI-generated Go code.
var nilMap map[string]int // nil - declared but not initialized
emptyMap := map[string]int{} // empty - initialized, zero entries
// Reading from nil is fine
fmt.Println(nilMap["key"]) // 0
// Writing to nil panics
nilMap["key"] = 1 // panic: assignment to entry in nil map| Operation | Nil map | Empty map |
|---|---|---|
len(m) | 0 | 0 |
Read m[key] | Returns zero value | Returns zero value |
Write m[key] = v | panic | Works |
delete(m, key) | No-op (safe) | No-op (safe) |
for range m | Zero iterations | Zero iterations |
| JSON marshal | null | {} |
New function. The struct gets created, code reads from the map fine (returns zero values), then eventually something writes to it and the program panics. Always check that map fields are initialized with make() or a literal before any write path.Deleting entries
users := map[string]int{"Alice": 1, "Bob": 2, "Charlie": 3}
delete(users, "Bob")
delete(users, "Nobody") // safe - no panic, no errordelete() returns nothing. If you need to know whether the key existed, check with comma-ok first.
Iteration order is random
Go intentionally randomizes map iteration order. Every for range over the same map can produce a different sequence.
m := map[string]int{"a": 1, "b": 2, "c": 3}
for k, v := range m {
fmt.Printf("%s: %d\n", k, v)
}
// Output order is different each runIf you need deterministic order, extract and sort the keys:
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Printf("%s: %d\n", k, m[k])
}Or use slices.Sorted(maps.Keys(m)) in Go 1.23+.
Maps and concurrencyWhat is concurrency?The ability of a program to handle multiple tasks at the same time, like serving thousands of users without slowing down.: the big one
Maps are not safe for concurrent access. Reading from a map while another goroutineWhat is goroutine?A lightweight concurrent function in Go launched with the go keyword - much cheaper than an OS thread (~2KB vs ~1MB of stack). writes to it causes a fatal runtimeWhat is runtime?The environment that runs your code after it's written. Some languages need a runtime installed on the machine; others (like Go) bake it into the binary. crash, not a data raceWhat is data race?A concurrency bug where two threads access the same memory location at the same time and at least one is writing, producing unpredictable results. warning, not corrupted data, but an immediate program termination.
// This WILL crash
m := make(map[string]int)
go func() {
for i := 0; i < 1000; i++ {
m["key"] = i
}
}()
go func() {
for i := 0; i < 1000; i++ {
_ = m["key"]
}
}()Go's runtime has a built-in concurrent map access detector that panics with fatal error: concurrent map read and map write.
How to fix it
| Approach | When to use |
|---|---|
sync.Mutex | Simple cases, few goroutines |
sync.RWMutex | Many readers, few writers |
sync.Map | High contention, keys are stable over time |
| Channel-based access | When you're already using channels for coordination |
// sync.RWMutex approach
type SafeMap struct {
mu sync.RWMutex
m map[string]int
}
func (s *SafeMap) Get(key string) (int, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
v, ok := s.m[key]
return v, ok
}
func (s *SafeMap) Set(key string, value int) {
s.mu.Lock()
defer s.mu.Unlock()
s.m[key] = value
}map[string]something variables and then accessing them from HTTP handlers (which run concurrently). This code works in manual testing but crashes under load. Whenever you see a package-level map that could be touched by multiple goroutines, it needs synchronization. sync.Map is the quickest fix but not always the most efficient.Maps as sets
Go doesn't have a built-in set type. The idiomatic workaround is map[T]struct{}:
seen := make(map[string]struct{})
seen["apple"] = struct{}{}
seen["banana"] = struct{}{}
if _, ok := seen["apple"]; ok {
fmt.Println("already seen apple")
}Why struct{} instead of bool? The empty struct takes zero bytes of storage. For large sets, this adds up.
Practical pattern: grouping with maps
AI generates this pattern frequently. Make sure the inner slice is initialized:
type Order struct {
Customer string
Amount float64
}
func groupByCustomer(orders []Order) map[string][]Order {
groups := make(map[string][]Order)
for _, o := range orders {
groups[o.Customer] = append(groups[o.Customer], o)
}
return groups
}This works because append() handles nil slices, append(nil, x) returns []T{x}. So the first time a customer appears, their nil slice gets an element appended.