Maps

Reviewed & published by Brayan K

A map is Go's built-in key/value lookup table — the data structure you reach for whenever you need to find something by name rather than by position. You'll learn to create maps, read and write entries, safely check whether a key exists with the "comma ok" idiom, delete keys, and tally data with the classic counting pattern.

Part of the free Go course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ Creating & Accessing

A map's type is map[KeyType]ValueType — for example map[string]int. Build one with make or a literal, then read and write entries with square brackets. A crucial Go quirk: reading a key that isn't there does not error — it returns the value type's zero value (0, "", false). That's convenient, but it's why you need the comma-ok check below.

package main

import "fmt"

func main() {
    // A map is a key -> value lookup table. Type: map[KeyType]ValueType.

    // make builds an empty, ready-to-use map.
    ages := make(map[string]int)
    ages["Alice"] = 30
    ages["Bob"] = 25
    fmt.Println(ages["Alice"]) // 30

    // Map literal — set entries up front.
    capitals := map[string]string{
        "France": "Paris",
        "Japan":  "Tokyo",
    }
    fmt.Println(capitals["Japan"]) // Tokyo

    // Reading a MISSING key returns the value type's ZERO value,
    // never an error — so this is 0, not a crash.
    fmt.Println("missing:", ages["Nobody"]) // missing: 0

    fmt.Println("entries:", len(ages)) // entries: 2
}

2️⃣ comma-ok & delete

Because a missing key returns a zero value, you can't tell "the value really is 0" from "the key isn't here" by reading alone. The comma-ok form solves this: value, ok := m[key] gives you the value and a boolean that's true only when the key exists. Remove an entry with the built-in delete(m, key), which is safe even if the key was never there.

package main

import "fmt"

func main() {
    inventory := map[string]int{
        "apples":  5,
        "bananas": 0, // we HAVE a bananas entry, the count is just 0
    }

    // The "comma ok" form tells you whether a key EXISTS,
    // separate from its value. This distinguishes "0 count"
    // from "not in the map at all".
    count, ok := inventory["bananas"]
    fmt.Println("bananas:", count, "present?", ok) // bananas: 0 present? true

    _, ok2 := inventory["cherries"]
    fmt.Println("cherries present?", ok2)           // cherries present? false

    // delete removes a key (safe even if the key is absent).
    delete(inventory, "apples")
    fmt.Println("after delete:", len(inventory)) // after delete: 1
}

3️⃣ Ranging & the Count Pattern

You range over a map to get keys and values, but remember the order is randomized on every run — never depend on it. For stable output, collect the keys into a slice and sort them. The single most common map idiom is counting: counts[w]++ works even for a brand-new key because the missing value starts at the zero value 0.

package main

import (
    "fmt"
    "sort"
)

func main() {
    // Classic use: count occurrences of each word.
    words := []string{"go", "rust", "go", "go", "rust"}
    counts := map[string]int{}      // empty literal, same as make
    for _, w := range words {
        counts[w]++                 // missing key starts at 0, then +1
    }
    // counts is now map[go:3 rust:2]

    // Map iteration order is RANDOM. For stable output, sort the keys.
    keys := make([]string, 0, len(counts))
    for k := range counts {
        keys = append(keys, k)
    }
    sort.Strings(keys)

    for _, k := range keys {
        fmt.Printf("%s: %d\n", k, counts[k])
    }
}

Your turn — build a small price list and read one back.

package main

import "fmt"

func main() {
    // 🎯 YOUR TURN — replace each ___ then run it.

    // 1) Make an empty map from string to int (a price list)
    prices := ___(map[string]int)   // 👉 make

    // 2) Add two products
    prices["pen"] = 2
    prices["pad"] = 5

    // 3) Read the price of a pad
    fmt.Println("pad costs", prices[___]) // 👉 "pad"

    // ✅ Expected output:  pad costs 5
}

Now use the comma-ok idiom to check whether a user is registered.

package main

import "fmt"

func main() {
    // 🎯 YOUR TURN — finish the existence check then run it.
    users := map[string]bool{"ada": true}

    if _, ___ := users["ada"]; ok {   // 👉 ok   (the "comma ok" variable)
        fmt.Println("ada is registered")
    } else {
        fmt.Println("no such user")
    }

    // ✅ Expected output:  ada is registered
}

🧩 Reorder Challenge

These lines count a key twice, but they're scrambled. Put them in order so the program prints 2 (assume a func main wraps them).

    m["a"]++
    fmt.Println(m["a"])
    m := map[string]int{}
    m["a"]++

Why: the map must exist before any key can be incremented. The first m["a"]++ turns the missing-key zero into 1; the second makes it 2. Printing m["a"] last yields 2. Both increments must precede the print.

Pro Tips

Common Errors (and the fix)

📋 Quick Reference

TaskSyntax
Makemake(map[string]int)
Literalmap[string]int{"a": 1}
Set / getm["a"] = 1; x := m["a"]
Exists?v, ok := m["a"]
Deletedelete(m, "a")
Count entrieslen(m)

🧠 Quick Recall

Predict the output before you reveal the answer.

0 — reading a missing key returns the value type's zero value, which is 0 for int. No error, no panic.

m := map[string]int{"a": 0}; _, ok := m["a"]; fmt.Println(ok)

true — the key "a" exists (its value just happens to be 0), so the comma-ok boolean is true.

m := map[string]int{}; m["go"]++; m["go"]++; fmt.Println(m["go"])

2 — the first increment takes the implicit 0 to 1, the second to 2. This is the counting idiom in action.

Frequently Asked Questions

Q: Why does reading a missing key not crash?

Go returns the value type's zero value instead, which keeps lookup code simple. Use the comma-ok form (v, ok := m[k]) when you must know whether the key actually exists.

Q: Can I use any type as a key?

Only comparable types: strings, numbers, booleans, arrays, and structs of comparable fields. Slices, maps, and functions can't be keys because they aren't comparable with ==.

Q: How do I get a sorted list of keys?

Range the map to collect keys into a slice, then call sort.Strings (or sort.Ints). Iterate that sorted slice for deterministic output.

Mini-Challenge: Tally Letters

Range over the word's runes and count each letter into a map[rune]int, then print the tally for the letter 'l'. Write it yourself and match the example output.

package main

import "fmt"

func main() {
    // 🎯 MINI-CHALLENGE: Tally letters
    // Count how many times each letter appears in the word below.
    // Range over the string (runes), use a map[rune]int, and
    // print the count for 'l'.
    //
    // ✅ Example output (for "hello"):  l appears 2 times

    word := "hello"
    // your code here
}

🎉 Lesson Complete!

Practice quiz

What is the type syntax for a Go map from string keys to int values?

  • map<string,int>
  • map[string]int
  • [string]int
  • dict[string]int

Answer: map[string]int. Go maps are written map[KeyType]ValueType, e.g. map[string]int.

What does a map return when you read a key that is absent?

  • An error
  • nil
  • The value type's zero value
  • A panic

Answer: The value type's zero value. A missing key returns the value type's zero value — never an error.

Which form distinguishes 'absent' from 'present but zero'?

  • v := m[k]
  • v, ok := m[k]
  • m.Get(k)
  • len(m[k])

Answer: v, ok := m[k]. The comma-ok form v, ok := m[k] sets ok=false when the key is missing.

How do you remove an entry from a map?

  • remove(m, k)
  • m.delete(k)
  • delete(m, k)
  • m[k] = nil

Answer: delete(m, k). The built-in delete(m, k) removes the entry for key k.

What does len(m) return for a map?

  • The capacity
  • The number of key/value pairs
  • Always 0
  • The size in bytes

Answer: The number of key/value pairs. len(m) counts how many key/value pairs the map currently holds.

What is the iteration order when you range over a map?

  • Insertion order
  • Sorted by key
  • Random / unspecified
  • Reverse insertion

Answer: Random / unspecified. Map iteration order is randomized; sort the keys yourself for stable output.

Which built-in creates an empty, usable map?

  • new(map)
  • make(map[string]int)
  • map.New()
  • alloc()

Answer: make(map[string]int). make(map[K]V) creates an initialized map ready for writes.

What does m[k]++ do when k is not yet in the map?

  • Panics
  • Starts from the zero value, so becomes 1
  • Does nothing
  • Returns an error

Answer: Starts from the zero value, so becomes 1. Reading a missing int key yields 0, so m[k]++ cleanly sets it to 1 — handy for tallies.

What happens if you write to a nil map?

  • It is created
  • It panics
  • It is ignored
  • It returns false

Answer: It panics. Writing to a nil map panics; initialize with make or a literal first.

To print map entries in a stable order, you should:

  • Use a special flag
  • Collect keys, sort them, then range over keys
  • Nothing — it is already sorted
  • Convert to a slice automatically

Answer: Collect keys, sort them, then range over keys. Gather the keys into a slice, sort them, then iterate in that order.

Continue this course