Go/
Lesson

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 typeUse case
map[string]intWord counts, scores, frequencies
map[int]stringID-to-name lookups
map[string][]stringGrouping (tags to items, roles to users)
map[string]struct{}Sets (the empty struct uses zero memory)
map[string]map[string]intNested lookups (use sparingly)
02

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")
}
AI pitfall
AI frequently generates 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.
03

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
OperationNil mapEmpty map
len(m)00
Read m[key]Returns zero valueReturns zero value
Write m[key] = vpanicWorks
delete(m, key)No-op (safe)No-op (safe)
for range mZero iterationsZero iterations
JSON marshalnull{}
AI pitfall
When AI generates a struct with a map field, it often forgets to initialize the map in the constructor or 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.
04

Deleting entries

users := map[string]int{"Alice": 1, "Bob": 2, "Charlie": 3}
delete(users, "Bob")
delete(users, "Nobody") // safe - no panic, no error

delete() returns nothing. If you need to know whether the key existed, check with comma-ok first.

05

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 run

If 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+.

AI pitfall
AI-generated tests that iterate over maps and compare output strings will be flaky because the order changes between runs. If you see AI write a test that builds a string by ranging over a map and then asserts the exact string, that test will intermittently fail.
06

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

ApproachWhen to use
sync.MutexSimple cases, few goroutines
sync.RWMutexMany readers, few writers
sync.MapHigh contention, keys are stable over time
Channel-based accessWhen 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
}
AI pitfall
AI loves generating global 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.
07

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.

08

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.