Fix

"assignment to entry in nil map" in Go

This error occurs because you are trying to assign a value to a map key without first initializing the map with `make`.

The panic that kills your program

You declare a map to store user roles. You write var roles map[string]string. You feel good. The syntax looks correct. You assign roles["alice"] = "admin". You run the program. The terminal screams panic: assignment to entry in nil map. The program dies. You stare at the code. The type is right. The variable exists. Why did it crash?

This panic is one of the most common runtime errors for new Go developers. It happens because the map variable exists, but the map data does not. The variable holds a nil reference. You cannot write to a nil map. You must allocate the underlying data structure before assigning values.

The map handle and the missing bucket

Go maps are reference types. The variable you declare is not the map itself. It is a handle pointing to the map data somewhere in memory. When you write var roles map[string]string, you create the handle. The handle exists. It points to nowhere. It is nil.

Assigning a value means putting data into the map structure. If the handle points to nowhere, there is no structure to put data into. The runtime panics because it cannot write to a ghost.

Reading from a nil map is safe. It returns the zero value for the value type. Writing is the problem. You need to allocate the map data before the handle can point to it. That is what make does. make(map[string]string) allocates the map structure on the heap and returns a handle pointing to it. The variable is no longer nil. You can write values.

package main

import "fmt"

func main() {
	// Declare a map variable. The variable exists, but the map data does not.
	// The variable holds a nil reference.
	var users map[string]string

	// This line panics. The runtime tries to write to memory that hasn't been allocated.
	// Error: panic: assignment to entry in nil map
	users["alice"] = "admin"

	// Fix: Allocate the map data structure using make.
	// Now the variable points to a real, empty map ready for entries.
	users = make(map[string]string)
	users["alice"] = "admin"
	users["bob"] = "guest"

	fmt.Println(users)
}

Go developers often skip the var declaration and initialize immediately. users := make(map[string]string) is the standard pattern. It combines declaration and allocation in one step. This reduces the window where the variable is nil.

Nil maps are read-only. Allocate before you write.

Why the compiler stays silent

The compiler does not catch this error. The type of users is map[string]string. A nil map has the correct type. The compiler sees a valid assignment to a map variable and allows it. The panic happens at runtime.

The Go runtime checks the map header before every write operation. If the header is nil, it triggers the panic. This design choice keeps the compiler fast and the type system simple. The trade-off is that nil map writes are runtime errors, not compile-time errors. You have to run the code or write tests to find the bug.

The compiler trusts your types. The runtime checks your memory.

Realistic scenario: Struct fields

Maps inside structs are a frequent source of this panic. Struct fields initialize to their zero value. The zero value for a map is nil. If you create a struct and try to write to a map field without initializing it, the program crashes.

package main

import "fmt"

// Config holds application settings.
// The Settings field is a map. Struct fields initialize to their zero value.
// The zero value for a map is nil.
type Config struct {
	Settings map[string]int
}

// LoadConfig creates a Config and populates settings.
func LoadConfig() Config {
	var cfg Config
	// cfg.Settings is nil here.
	// Attempting to write causes a panic.
	// cfg.Settings["timeout"] = 30 // Panic

	// Initialize the map before writing.
	cfg.Settings = make(map[string]int)
	cfg.Settings["timeout"] = 30
	cfg.Settings["retries"] = 3

	return cfg
}

func main() {
	cfg := LoadConfig()
	fmt.Println(cfg.Settings)
}

When defining structs, initialize maps in the struct literal. cfg := Config{Settings: make(map[string]int)} is safer. It guarantees the map is allocated before any code runs. This pattern prevents nil map panics in complex initialization logic.

Initialize maps at the source. Don't let nil travel.

Zero value semantics: Reading is safe

Go guarantees that reading a map key returns the zero value if the key is missing. This rule applies even when the map is nil. If you have var scores map[string]int and you run score := scores["alice"], the result is 0. The runtime does not panic on reads.

This design allows nil maps to function as empty maps for read-only operations. You can pass a nil map to a function that only reads data. The function works correctly. This behavior simplifies code paths where a map might not be initialized yet. You can check for nil if you need to distinguish between "map is empty" and "map is nil", but for pure reads, nil behaves like an empty map.

Capacity hints

When you call make(map[K]V), the runtime allocates a small initial bucket. As you add entries, the map grows automatically. If you know the approximate size, pass a hint to make. make(map[string]int, 1000) tells the runtime to reserve space for roughly 1000 entries. This reduces the number of reallocations during growth.

The hint is not a hard limit. The map still grows if you exceed it. Use the hint when you have a known dataset size to improve performance. Pre-allocating capacity is a common optimization in Go.

make versus new

The new function allocates zeroed memory and returns a pointer. new(map[string]string) returns a *map[string]string pointing to a nil map. This is rarely useful. You end up with a pointer to a nil handle. Dereferencing it gives a nil map. You still cannot write to it.

Use make for maps, slices, and channels. Use new only when you need a pointer to a zeroed value, which is uncommon for maps. The community convention is clear: make initializes complex types. new allocates raw memory.

make allocates. new points. Use make for maps.

Pitfalls: Passing maps to functions

Maps are reference types, but the variable holding the map is a value. When you pass a map to a function, Go copies the map header. The copy points to the same underlying data. Changes to the map contents are visible to the caller. Reassigning the map variable inside the function does not affect the caller. The function gets a new handle. The caller keeps the old handle.

If the caller had a nil map, the function reassigns its local copy to a new map. The caller's variable remains nil. To update the caller's variable, pass a pointer to the map variable. func Init(m *map[string]string) allows the function to write to the caller's handle. This pattern is necessary when the map needs to be initialized inside the function and the caller must see the result.

package main

import "fmt"

// AddUser adds a user to the roles map.
// If the map is nil, this function panics.
func AddUser(roles map[string]string, name, role string) {
	roles[name] = role
}

// SafeAddUser checks for nil before writing.
// This pattern is useful when the map might be uninitialized.
func SafeAddUser(roles map[string]string, name, role string) map[string]string {
	if roles == nil {
		roles = make(map[string]string)
	}
	roles[name] = role
	return roles
}

// InitMap demonstrates updating the caller's map variable.
// It takes a pointer to the map variable.
func InitMap(m *map[string]string) {
	if *m == nil {
		*m = make(map[string]string)
	}
	(*m)["default"] = "value"
}

func main() {
	var roles map[string]string

	// AddUser panics because roles is nil.
	// AddUser(roles, "alice", "admin")

	// SafeAddUser handles the nil case and returns the initialized map.
	roles = SafeAddUser(roles, "alice", "admin")
	fmt.Println(roles)

	// InitMap updates the roles variable directly via pointer.
	InitMap(&roles)
	fmt.Println(roles)
}

Pass maps by value. Pass pointers to update the handle.

Decision matrix

Use make(map[K]V) when you need to write values to the map.

Use var m map[K]V when you only need to read values and expect the zero value for missing keys.

Use m := map[K]V{} when you want a concise declaration that allocates an empty map immediately.

Use a nil map check when a function receives a map that might be uninitialized and needs to initialize it locally.

Use a pointer to a map (*map[K]V) when a function must update the caller's map variable itself, not just the contents.

Use make(map[K]V, hint) when you know the approximate number of entries and want to reduce reallocations.

Use struct literal initialization when defining structs with map fields to guarantee allocation at creation time.

Where to go next