A map is a collection of key-value pairs. Instead of looking up by index (like in a slice), you look up by a key — a username, a country code, a product ID.

If you’ve used Python’s dict, JavaScript’s Object, or Java’s HashMap, this is the same idea.

Creating a map

package main

import "fmt"

func main() {
    capitals := map[string]string{
        "India":    "New Delhi",
        "France":   "Paris",
        "Japan":    "Tokyo",
        "Brazil":   "Brasília",
    }

    fmt.Println(capitals)
    fmt.Println("Capital of India:", capitals["India"])
}
map[Brazil:Brasília France:Paris India:New Delhi Japan:Tokyo]
Capital of India: New Delhi

Reading the type map[string]string:

  • map — this is a map
  • [string] — keys are strings
  • string — values are strings

You can have any combination: map[string]int, map[int]string, even nested maps.

Maps are not ordered. The order of keys when you iterate or print a map is intentionally randomized in Go. If you depend on order, use a slice or sort the keys explicitly.

Adding, updating, and reading

// inside main()
capitals := map[string]string{
    "India":  "New Delhi",
    "France": "Paris",
}

capitals["Japan"] = "Tokyo"          // add new key
capitals["France"] = "PARIS"         // update existing key

city := capitals["India"]            // read
fmt.Println(city)

Adding and updating use the same syntax — Go figures out which is happening based on whether the key already exists.

The “comma ok” idiom

What happens if you read a key that doesn’t exist?

// inside main()
capitals := map[string]string{"India": "New Delhi"}
city := capitals["Mars"]
fmt.Println(city)
fmt.Println(len(city))

0

It returned the zero value of the value type — for strings, that’s "". No error, no crash. This is convenient sometimes, but dangerous if you can’t tell “missing key” from “key with empty value.”

The fix is the “comma ok” idiom — Go returns two values when you read from a map:

// inside main()
capitals := map[string]string{"India": "New Delhi"}

if city, ok := capitals["Mars"]; ok {
    fmt.Println("Capital:", city)
} else {
    fmt.Println("Mars not in map")
}
Mars not in map

The second return value (ok) is a booltrue if the key was present, false if not. This is the canonical way to check for a key in Go. Use it whenever the difference matters.

Deleting

Use the built-in delete function:

// inside main()
capitals := map[string]string{
    "India":  "New Delhi",
    "France": "Paris",
}

delete(capitals, "France")
fmt.Println(capitals)
map[India:New Delhi]

If the key doesn’t exist, delete does nothing. No error.

Iterating

Use range — same as with slices, but you get key and value instead of index and value:

// inside main()
capitals := map[string]string{
    "India":  "New Delhi",
    "France": "Paris",
    "Japan":  "Tokyo",
}

for country, city := range capitals {
    fmt.Println(country, "->", city)
}
Japan -> Tokyo
India -> New Delhi
France -> Paris

(The order will be different every time you run it. That’s a feature, not a bug — it stops you from accidentally writing code that depends on an order Go doesn’t promise.)

Creating an empty map

Two ways to create a map you’ll fill in later:

// inside main()
votes := make(map[string]int)        // works — ready to use
votes["yes"]++                        // 1
votes["no"]++                         // 1
votes["yes"]++                        // 2
fmt.Println(votes)
map[no:1 yes:2]

A map declared with just var m map[string]int is nil — you can read from it (returning zero values) but writing crashes the program. Always use make or a literal {}.

A practical example

A word-count program. Read a sentence, count how often each word appears:

package main

import (
    "fmt"
    "strings"
)

func main() {
    text := "the quick brown fox jumps over the lazy dog the fox is quick"

    counts := make(map[string]int)
    for _, word := range strings.Fields(text) {
        counts[word]++
    }

    for word, count := range counts {
        fmt.Println(word, ":", count)
    }
}
the : 3
quick : 2
brown : 1
fox : 2
jumps : 1
over : 1
lazy : 1
dog : 1
is : 1

strings.Fields splits a string by whitespace. The counts[word]++ line is the magic: if word isn’t in the map, counts[word] returns 0 (the zero value for int), then ++ makes it 1 and stores it. If it’s already there, ++ increments it. No special-casing needed.

Word counting in fewer than 15 lines is the kind of thing that makes Go pleasant to write.

What’s next

You can store ordered lists (slices) and key-value pairs (maps). Real programs also need structured records — a customer with a name, email, and address held together. That’s what structs are for, and we’ll learn them alongside pointers in the next lesson.

Toggle theme (T)