Alright, let’s talk about one of Go’s more infamous party tricks: the nil map. You’ve probably seen it. You initialize a map with var, try to put a key into it, and the runtime slaps you down with a panic: assignment to entry in nil map. It feels a bit dramatic, doesn’t it? Like your car refusing to start because you didn’t say please. But there’s a method to this madness, and understanding it is key to not having your Friday evening debugging session ruined.

The core of the issue is that in Go, a nil map and an empty map are two very different beasts, and only one of them is actually usable.

The Anatomy of a Nil Map

When you declare a map using var, you’re essentially creating a map variable that’s a reference type set to its zero value: nil. It’s not pointing to anything. It’s a placeholder for a map, not a map itself.

var myMap map[string]int // myMap is nil
fmt.Println(myMap == nil) // true

Think of it like writing an address on a sticky note but never actually building the house. You can read the address (the nil value) all day long, but if you try to open the front door and put a sofa inside (myMap["key"] = 42), you’re going to have a very bad time because there’s no house. The runtime panics because it has nowhere to put your data.

The Empty but Initialized Map

This is the usable one. You create it with the make function, which actually goes out, builds the house (allocates the underlying hash data structure), and gives you the real address.

myInitializedMap := make(map[string]int) // myInitializedMap is NOT nil
fmt.Println(myInitializedMap == nil) // false
myInitializedMap["key"] = 42 // This works perfectly

You can also use a composite literal, which does the make for you under the hood:

myOtherMap := map[string]int{} // Also NOT nil, also perfectly usable

Both make(map[string]int) and map[string]int{} give you an empty but initialized map. It’s a house with no furniture, but you can absolutely move furniture in whenever you like.

Why The Panic? It’s a Design Choice

Go’s designers are notoriously pragmatic. A nil map isn’t just an empty map; it’s fundamentally unusable for writes. The panic is a deliberate, immediate feedback mechanism. It’s the language screaming at you, “You forgot to initialize this thing!” It’s far better than the alternative, which would be silently doing nothing or—worse—corrupting some random part of memory. This fail-fast behavior saves you from incredibly subtle and nasty bugs that would be a nightmare to track down later. They chose a moment of sharp pain over long, lingering suffering. I happen to agree with them.

Reading from a Nil Map is Perfectly Fine

Here’s the twist that sometimes confuses people: while you cannot write to a nil map, you can read from it.

var nilMap map[string]int
value := nilMap["key"] // This is fine. Value will be the zero value for int (0).
fmt.Println(value) // Prints: 0

_, exists := nilMap["key"] // This is also fine. Exists will be false.
fmt.Println(exists) // Prints: false

This works because the operation of looking up a key doesn’t require modifying the map. The runtime logic is: “If the map is nil, then by definition no key has ever been stored in it, so I should return the zero value and false.” It’s consistent and safe.

The Pitfall: Returning Nil from a Function

The most common way this bites experienced developers is when returning a map from a function. Consider this:

func getConfig() map[string]string {
    var config map[string]string
    // ... some logic that might not populate config ...
    return config // Oops, returning a nil map
}

func main() {
    config := getConfig()
    config["timeout"] = "30s" // PANIC! assignment to entry in nil map
}

Your function should never return a nil map if it intends for the caller to use it as a map. If there’s a chance your logic might not initialize it, you must return an initialized empty map instead.

func getConfigSafe() map[string]string {
    config := make(map[string]string) // Initialize it upfront
    // ... if logic doesn't populate it, it's just empty, not nil ...
    return config // This is safe to write to
}

Best practice: Always use make or a composite literal to initialize a map if you plan to write to it. Treat a var declaration for a map as a red flag until you prove otherwise. It’s a sign that you might only be planning to read from it, or that you’re initializing it later under a specific condition. If that condition isn’t met, you’ve built a trap for yourself. Initialize early and often. Your panic-free runtime will thank you.