Right, let’s talk about the three things you actually do with a map: putting stuff in, getting stuff out, and (occasionally) blowing stuff up. It seems simple, right? map[key] = value. And for the happy path, it is. But the devil, as always, is in the details, and he’s a particularly pedantic programmer.

The Assignment Operator: Your Best Friend and Worst Enemy

You’ve seen it a million times. You want to add or update a value, so you use the assignment operator.

myMap := make(map[string]int)
myMap["answer"] = 42 // Writing
fmt.Println(myMap["answer"]) // Reading: prints 42
myMap["answer"] = 24 // Updating
fmt.Println(myMap["answer"]) // Reading: prints 24

This is the workhorse. But here’s the first “gotcha” that Go’s designers, in their infinite wisdom, decided was a good idea: reading a key that doesn’t exist doesn’t throw an error. It doesn’t panic. It just gives you the zero value for the map’s value type. This is either brilliantly convenient or a silent bug factory, depending on whether you just got burned by it.

fmt.Println(myMap["question"]) // Reads a non-existent key. Prints: 0

Is that 0 the value you stored, or is it the map telling you it has no idea what you’re talking about? You can’t tell. And that’s a problem.

The Comma-Ok Idiom: Your Truth Detector

To solve that existential dread, Go gives you the “comma-ok” idiom. It’s not a fancy method call; it’s just a second return value from a map read operation.

value, exists := myMap["question"]
if exists {
    fmt.Printf("The value is %d\n", value)
} else {
    fmt.Println("The key 'question' is a mystery to us all.")
}

You can also write it more idiomatically by just checking the value itself, though I find it less explicit.

if value, ok := myMap["question"]; ok {
    // Use value
}

This is non-negotiable. You must use this pattern any time you cannot be 100% certain a key exists in the map. Trust me, your future self, debugging a weird 0 or "" value at 2 AM, will thank you.

Deletion: Making Things Vanish

Deleting an entry is straightforward. You use the delete built-in function. It’s a function, not a method on the map, which is a bit quirky but you get used to it.

myMap["temporary"] = 100
fmt.Println(myMap) // map[answer:24 temporary:100]
delete(myMap, "temporary")
fmt.Println(myMap) // map[answer:24]

Here’s the beautiful part: delete is a no-op if the key doesn’t exist. It doesn’t panic. It just shrugs and carries on. This is one of those design choices I actually agree with. It simplifies code where you might need to clean up a key without first checking for its existence.

The Nil Map Trap: A Classic Blunder

This is the big one. The pit of despair every Go developer falls into at least once. Behold the nil map:

var nilMap map[string]int // This map is nil. It's zero-initialized.
fmt.Println(nilMap == nil) // true

This map is a black hole. It will compile perfectly. But trying to write to it will cause a panic.

nilMap["doom"] = 1 // panic: assignment to entry in nil map

Reading from a nil map, however, is perfectly safe. It will just return the zero value, as if the key didn’t exist. The delete function will also work fine on a nil map (it’s a no-op, remember?). The asymmetry is, frankly, a bit absurd. The solution is simple: always initialize your maps with make or a literal.

// Good. Do this.
safeMap := make(map[string]int)
// Also good.
saferMap := map[string]int{}

Iteration: The Unordered Truth

I need to be direct with you: maps are unordered by design. The runtime intentionally randomizes iteration order. This isn’t a suggestion; it’s a feature to prevent you from writing code that accidentally depends on an ordering that isn’t guaranteed. If you need order, you must manage it yourself, typically by keeping a separate slice of keys and sorting it.

myMap := map[string]int{"zebra": 2, "alpha": 1, "beta": 3}

// This will print in a random order every run.
for key, value := range myMap {
    fmt.Printf("%s: %d\n", key, value)
}

// To get a sorted order:
keys := make([]string, 0, len(myMap))
for k := range myMap {
    keys = append(keys, k)
}
sort.Strings(keys)
for _, key := range keys {
    fmt.Printf("%s: %d\n", key, myMap[key])
}

Yes, it’s a bit more boilerplate. No, there’s no way around it. This is the tax you pay for the incredible average O(1) time complexity of map operations. It’s a good trade. Embrace it.