# Maps

Course: [Go](https://www.devtutorials.ai/learn/go) · Section: Collections
Source: https://www.devtutorials.ai/learn/go/collections/maps

> Go maps: key-value pairs. Create them with literals and make, add, update, delete and look up values, check keys with comma-ok, loop with range, and use zero values and nested maps.

## What Are Maps?

**Map** = a collection of **key and value** pairs, where you find a value by its key.

- **Key** = the label you look up, like a student's name.
- **Value** = what is stored for that key, like their marks.
- Every key is **unique**. One key has one value.

**Simple lesson:** a map stores values by name, so you can find any value straight away.

### A Real-World Example

Think of the school's phone directory.

- Each line has a **name** and a **phone number**.
- To call the principal, you look for "Principal", not for line number 17.
- Each name appears only once.
- New names can be added, old ones changed or removed.

Now the mapping:

- **The directory** = the map.
- **The name** = the key.
- **The phone number** = the value.
- **Looking up a name** = `directory["Principal"]`.

Slices find values by **position**: `marks[2]`.

Maps find values by **key**: `marks["Asha"]`.

Use a map when you think "look up X by its name".

## What You Will Learn

In this lesson, you will learn:

1. What maps and key-value pairs are.
2. How to create maps with a literal or with `make()`.
3. How to add, update, delete and look up values.
4. How to check if a key exists with the comma-ok idiom.
5. How to loop over a map with `range`.
6. Map best practices: zero values and nested maps.

## Map Fundamentals

### Key-Value Pairs

A map type is written `map[KeyType]ValueType`.

- `map[string]int` = keys are strings, values are ints.
- `map[int]string` = keys are ints, values are strings.
- Keys must be **comparable** with `==`: strings, numbers, bools, arrays and simple structs work. Slices and maps cannot be keys.

### Creating Maps

```go runnable title="Create maps"
package main

import "fmt"

func main() {
	marks := map[string]int{
		"Asha": 91,
		"Ravi": 78,
	}
	rooms := make(map[string]string)
	rooms["8A"] = "Room 12"
	fmt.Println(marks)
	fmt.Println(rooms, len(rooms))
}
```

```text
map[Asha:91 Ravi:78]
map[8A:Room 12] 1
```

- `fmt.Println` prints a map with its keys **sorted**, so the output is tidy.
- `len(m)` gives the number of pairs.

### make()

**make** = build a ready-to-use map (or slice, or channel).

- `make(map[string]int)` = an empty map.
- `make(map[string]int, 100)` = an empty map with room for about 100 pairs. It still grows if needed.

**Simple lesson:** write `map[K]V{...}` or `make(map[K]V)`, then use keys to reach values.

## Map Operations

Type a key and a value, then try every button.

Try these:

- **Set a new name:** a new pair is added.
- **Set an existing name:** its value is replaced.
- **Look up a missing name:** you get `0` and `ok = false`.
- **Delete a missing name:** nothing happens, and there is no error.

### Add and Update Elements

The same line adds **or** updates: `m[key] = value`.

```go runnable title="Add and update"
package main

import "fmt"

func main() {
	stock := map[string]int{"pencils": 40}
	stock["erasers"] = 25
	stock["pencils"] = 35
	stock["pencils"] += 10
	fmt.Println(stock)
}
```

```text
map[erasers:25 pencils:45]
```

- `"erasers"` was new, so it is **added**.
- `"pencils"` existed, so it is **updated**.
- `+=` works too: it reads, adds, then stores.

### Lookup Values

```go runnable title="Read values"
package main

import "fmt"

func main() {
	marks := map[string]int{"Asha": 91, "Ravi": 78}
	fmt.Println(marks["Asha"])
	fmt.Println(marks["Zoya"])
}
```

```text
91
0
```

- A **missing key** gives the **zero value** of the value type: `0` here.
- There is no error. So `0` could mean "scored 0" or "not in the map".

### Delete Elements

**delete()** = remove a key and its value.

```go runnable title="delete()"
package main

import "fmt"

func main() {
	club := map[string]string{"Asha": "8A", "Ravi": "8B", "Meena": "8A"}
	delete(club, "Ravi")
	delete(club, "Nobody")
	fmt.Println(club, len(club))
}
```

```text
map[Asha:8A Meena:8A] 2
```

- `delete(map, key)` removes the pair.
- Deleting a key that is not there is safe. Nothing happens.

**Simple lesson:** `m[k] = v` adds or updates, `m[k]` reads, and `delete(m, k)` removes.

## Map Lookup

### Comma-Ok Idiom

**Comma-ok idiom** = `value, ok := m[key]`. `ok` is `true` only if the key is really in the map.

Press **Play** to see both cases.

### Checking Key Existence

```go runnable title="Check before you use"
package main

import "fmt"

func main() {
	rooms := map[string]string{"8A": "Room 12", "8B": "Room 14"}
	if room, ok := rooms["8C"]; ok {
		fmt.Println("8C is in", room)
	} else {
		fmt.Println("No room set for 8C")
	}
	_, has8A := rooms["8A"]
	fmt.Println(has8A)
}
```

```text
No room set for 8C
true
```

- The `if` with a short statement keeps `room` and `ok` inside the `if`. See **Conditional Statements**.
- Use `_` when you only need to know if the key exists.

**Simple lesson:** use `v, ok := m[k]` to tell a real zero value from a missing key.

## Map Iteration

### range

`range` over a map gives each **key** and **value**.

```go runnable title="Loop over a map"
package main

import "fmt"

func main() {
	fees := map[string]int{"Asha": 1500, "Ravi": 1200, "Meena": 1500}
	total := 0
	for _, fee := range fees {
		total += fee
	}
	fmt.Println("Total fees:", total)
}
```

```text
Total fees: 4200
```

### The Order Changes

Go walks through a map in a **random order**, and the order changes from run to run.

```go runnable varies title="Run this twice: the order may change"
package main

import "fmt"

func main() {
	houses := map[string]int{"Red": 120, "Blue": 95, "Green": 110, "Yellow": 101}
	for house, points := range houses {
		fmt.Println(house, points)
	}
}
```

- Never depend on the order of a map loop.
- If you need an order, put the keys in a slice and sort it first.

```go runnable title="Print in sorted order"
package main

import (
	"fmt"
	"sort"
)

func main() {
	houses := map[string]int{"Red": 120, "Blue": 95, "Green": 110}
	names := make([]string, 0, len(houses))
	for h := range houses {
		names = append(names, h)
	}
	sort.Strings(names)
	for _, h := range names {
		fmt.Println(h, houses[h])
	}
}
```

```text
Blue 95
Green 110
Red 120
```

- `for h := range houses` gives only the keys.
- `sort.Strings` puts the keys in alphabetical order.

**Simple lesson:** `range` gives keys and values in a random order, so sort the keys when order matters.

## Map Best Practices

### Zero Values

The zero value makes counting easy: a missing key starts at `0`.

```go runnable title="Count votes for class monitor"
package main

import "fmt"

func main() {
	votes := []string{"Asha", "Ravi", "Asha", "Meena", "Asha", "Ravi"}
	count := make(map[string]int)
	for _, name := range votes {
		count[name]++
	}
	fmt.Println(count)
}
```

```text
map[Asha:3 Meena:1 Ravi:2]
```

- `count[name]++` works even the first time, because a missing key reads as `0`.

### Never Write to a nil Map

```go runnable expect-error title="Writing to a nil map"
package main

import "fmt"

func main() {
	var marks map[string]int
	fmt.Println(marks["Asha"])
	marks["Asha"] = 91
}
```

- Reading from a nil map is fine: it gives `0`.
- Writing to it stops the program: `panic: assignment to entry in nil map`.
- Fix: create it first with `make` or `{}`.

### Nested Maps

**Nested map** = a map whose values are maps.

```go runnable title="Marks by class, then by student"
package main

import "fmt"

func main() {
	school := map[string]map[string]int{
		"8A": {"Asha": 91, "Meena": 85},
		"8B": {"Ravi": 78},
	}
	fmt.Println(school["8A"]["Meena"])

	if school["8C"] == nil {
		school["8C"] = make(map[string]int)
	}
	school["8C"]["Kabir"] = 94
	fmt.Println(len(school), school["8C"])
}
```

```text
85
3 map[Kabir:94]
```

- `school["8A"]["Meena"]` reads the inner map, then the student.
- Before writing into a new inner map, create it. Otherwise it is nil.

Maps are **not safe** for two goroutines writing at the same time.

You will learn how to protect them in **Synchronization**.

**Easy way to remember**

- Add or update: `m[k] = v`
- Safe lookup: `v, ok := m[k]`
- Remove: `delete(m, k)`
- Order: none. Sort the keys if you need one.

**Simple lesson:** use the zero value for counting, always create a map before writing, and create inner maps too.

## TL;DR

1. **Map** = key-value pairs: `map[string]int`. Keys are unique.
2. **Create** = `map[K]V{...}` or `make(map[K]V)`. `var m map[K]V` is nil.
3. **Add / update** = `m[k] = v`. **Read** = `m[k]`. **Remove** = `delete(m, k)`.
   - **Length** = `len(m)`.
4. **Missing key** = the zero value, no error.
5. **Comma-ok** = `v, ok := m[k]`. `ok` tells you if the key exists.
6. **range** = `for k, v := range m`. The order is random.
7. **Best practices** = count with `m[k]++`, never write to a nil map, create inner maps first.

## Common mistakes

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| `var m map[string]int` then `m["a"] = 1` | `var` makes an empty map | It makes a nil map | `assignment to entry in nil map` |
| `if m["Zoya"] == 0` to check a key | 0 means missing | 0 may be a real value | Wrong answers. Use comma-ok. |
| Relying on loop order | Maps keep insertion order | The order is random | Output changes run to run. |
| `map[[]int]string` | Any type can be a key | Keys must be comparable | A build error. |
| Forgetting to create an inner map | Inner maps exist already | They start as nil | A panic on write. |
| `delete(m, k)` and checking an error | It returns an error | It returns nothing | A build error. |
| Two goroutines writing one map | Maps are thread-safe | They are not | `concurrent map writes` crash. |

## Check Yourself

## Exercises

## What to Remember

- A map stores values by key: `map[string]int`. Keys are unique.
- Create with a literal or `make`. Never write to a nil map.
- `m[k] = v` adds or updates. `delete(m, k)` removes.
- A missing key gives the zero value. Use `v, ok := m[k]` to be sure.
- Map loops have a random order. Sort the keys when you need one.

Next: **Pointers**. You will learn how a variable can point to another variable's box in memory.
