9.2 Reading, Writing, and Deleting Entries
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.