Golang - Data Structures - Maps In-Depth

Publish date: 2025-09-15
Tags: <a href="https://programmercave.com/tags/Go/">Go</a>

Introduction

A map in Go is a powerful, built-in data structure that associates keys of one type with values of another. It is an unordered collection, and it provides fast lookups, additions, and deletions. Maps are one of the most commonly used data structures in Go.

Analogy: Think of a map as a dictionary or a phone book. You look up a word (the key) to find its definition (the value). The keys must be unique.


Declaring and Initializing Maps

There are several ways to create a map.

package main

import "fmt"

func main() {
	// 1. Using the `make` function (most common)
	// Creates a map with string keys and int values
	ags := make(map[string]int)
	ags["Alice"] = 30
	ags["Bob"] = 25

	// 2. Using a map literal
	// Creates and initializes a map in one step
	scores := map[string]int{
		"Alice": 95,
		"Bob":   80,
	}

	// 3. Declaring a nil map
	var nilMap map[string]int
	fmt.Println("nilMap is nil?", nilMap == nil) // true
	// NOTE: You cannot add keys to a nil map! This will cause a runtime panic.
	// nilMap["Charlie"] = 50 // PANIC!

	fmt.Println(ages)
	fmt.Println(scores)
}

Key Takeaway: You must initialize a map before you can add elements to it. The idiomatic way is with make or a map literal.


Core Map Operations

1. Adding or Updating Elements

The syntax for adding and updating is the same.

m := make(map[string]int)
m["key1"] = 10 // Add
m["key1"] = 20 // Update

0221 - Data Structures - Map Internals and Performance

2. Retrieving Elements

You retrieve a value using its key.

value := m["key1"] // value will be 20

3. The “Comma Ok” Idiom

What if a key doesn’t exist? Accessing it will return the zero value for the value type (e.g., 0 for int, "" for string, false for bool). This can be ambiguous. Did you store a zero, or is the key missing?

To solve this, Go provides the “comma ok” idiom, which returns a second, boolean value indicating if the key was found.

package main

import "fmt"

func main() {
	scores := map[string]int{
		"Alice": 95,
		"Bob":   0, // Bob's score is explicitly zero
	}

	// Check for a key that exists
	score, ok := scores["Alice"]
	fmt.Printf("Alice's score: %d, Found: %t\n", score, ok) // 95, true

	// Check for a key that doesn't exist
	score, ok = scores["Charlie"]
	fmt.Printf("Charlie's score: %d, Found: %t\n", score, ok) // 0, false

	// Check for a key whose value is the zero value
	score, ok = scores["Bob"]
	fmt.Printf("Bob's score: %d, Found: %t\n", score, ok) // 0, true
}

Rule of Thumb: Always use the “comma ok” idiom when you need to distinguish between a missing key and a key that has a zero value.

4. Deleting Elements

Use the built-in delete function.

delete(scores, "Alice") // Removes the key "Alice"

Deleting a non-existent key does nothing and does not cause a panic.

5. Iterating Over a Map

Use a for...range loop.

for key, value := range scores {
    fmt.Printf("Key: %s, Value: %d\n", key, value)
}

CRITICAL: The iteration order of a Go map is not guaranteed. The order can be different every time you run the program. This is an intentional design choice to prevent developers from relying on a specific iteration order. If you need a stable order, you must extract the keys, sort them, and then iterate.

import "sort"

var keys []string
for k := range scores {
    keys = append(keys, k)
}
sort.Strings(keys) // Sort the keys
for _, k := range keys {
    fmt.Printf("Key: %s, Value: %d\n", k, scores[k]) // Iterate in sorted order
}

Map Keys

Using Structs as Keys

A struct can be a map key, but only if all of its fields are comparable. If a struct contains a slice, map, or function field, it cannot be used as a key.

package main

import "fmt"

// This struct IS comparable because its fields (int, string) are.
type user struct {
    id   int
    name string
}

func main() {
    // We can use the `user` struct as a map key
    lastLogin := make(map[user]string)

    u1 := user{id: 1, name: "Alice"}
    u2 := user{id: 2, name: "Bob"}

    lastLogin[u1] = "2025-09-07T10:00:00Z"
    lastLogin[u2] = "2025-09-07T10:05:00Z"

    fmt.Println("Alice's last login:", lastLogin[u1])

    // A struct with a slice field is NOT comparable.
    // type userWithPerms struct {
    // 	id    int
    // 	perms []string // This field makes the struct non-comparable
    // }
    // m := make(map[userWithPerms]bool) // This would cause a compile-time error
}

Arrays vs. Slices as Keys

This is a critical distinction that often comes up in interviews:

// An array can be a key
coordinates := make(map[[2]int]string)
coordinates[[2]int{10, 20}] = "Treasure"
fmt.Println(coordinates[[2]int{10, 20}]) // Output: Treasure

Pointers as Keys (A Common Gotcha)

While you can use pointers as map keys, it’s important to understand that the key is the pointer’s memory address, not the value it points to. Two different pointers that point to identical values are treated as two distinct keys.

package main

import "fmt"

func main() {
	a := 5
	b := 5

	// p1 and p2 point to different memory addresses, even though the values (5) are the same.
	p1 := &a
	p2 := &b

	m := make(map[*int]string)
	m[p1] = "Value for p1"

	fmt.Println("Using p1:", m[p1]) // "Value for p1"
	fmt.Println("Using p2:", m[p2]) // "" (empty string), because p2 is a different key
}

Key Takeaway: Use pointer keys only when you need to associate data with a specific instance of an object in memory. In most cases, using a value type (like a struct or an ID) as a key is safer and more predictable.

Tags: <a href="https://programmercave.com/tags/Go/">Go</a>