mapas-diccionarios-clave-valor-go

Maps in Go: key-value dictionaries and the comma ok pattern

  • 4 min

A map in Go is a key-value collection for looking up data by an identifier, like a dictionary where each key points to a specific value.

So far we’ve seen how to store data in a queue using arrays and slices. They work well if we know the order or the numeric index. To search by an ID, a name, or any other comparable key, we use maps.

In other languages, they are called dictionaries or hash maps. The concept is the same: a structure with no guaranteed iteration order that associates a unique key with a value.

What is a map in Go?

A map in Go is a reference to a hash table. The syntax to define it is: map[KeyType]ValueType.

  • Key type: can be any comparable type, such as booleans, numbers, strings, channels, pointers, and certain arrays, structs, and interfaces. It cannot be a slice, a map, or a function.
  • Value type: can be any type, even another map.

Declaration and initialization

Here’s a trap that almost all beginners fall into.

If you declare a map with just var, you get a nil map. A nil map behaves curiously:

  1. You can read from it (it returns zero values).
  2. You cannot assign an entry: it causes a panic.
package main

import "fmt"

func main() {
    // ❌ DANGEROUS WAY
    var colors map[string]string // It's nil
    // colors["red"] = "#FF0000" // ERROR: panic: assignment to entry in nil map

    // ✅ CORRECT WAY 1: make()
    // Initializes the map and reserves memory
    scores := make(map[string]int)
    scores["Luis"] = 10

    // ✅ CORRECT WAY 2: Literal
    // Very useful for initial values
    capitals := map[string]string{
        "Spain":   "Madrid",
        "France":  "Paris",
        "Italy":   "Rome", // Note the mandatory trailing comma!
    }

    fmt.Println(capitals)
}
Copied!

Use make(map[...]) or a literal if you are going to write to the map. A nil map is still useful as a read-only empty value.

Basic operations

Working with maps is very intuitive:

Insert or update

There is no difference. If the key doesn’t exist, it creates it. If it exists, it overwrites it.

m := make(map[string]int)
m["age"] = 30
m["age"] = 31 // Updates
Copied!

Read a value

value := m["age"]
Copied!

Delete a key

Go has a built-in function called delete.

delete(m, "age")
Copied!

Fun fact: If you try to delete a key that doesn’t exist, nothing happens (no error).

The zero value and the comma, ok pattern

What happens if you try to read a key that does not exist? In other languages, you’d get an exception or null. In Go, it returns the Zero Value of the value type.

scores := map[string]int{"Anna": 10}

grade := scores["Pedro"] // Pedro doesn't exist
fmt.Println(grade) // Prints 0 (because it's the zero value of int)
Copied!

This is a problem. Does Pedro have a 0 because he failed, or does he have a 0 because he didn’t take the exam (he isn’t in the map)?

To distinguish these cases, we use the multiple assignment, known as the “Comma Ok” pattern:

value, ok := map[key]
Copied!
  • value: The stored value (or zero if not present).
  • ok: A boolean (true if the key exists, false if not).
if grade, ok := scores["Pedro"]; ok {
    fmt.Println("Pedro's grade is:", grade)
} else {
    fmt.Println("Pedro is not on the list")
}
Copied!

This pattern is common in Go. Use it when you need to distinguish between an absent key and a stored zero value.

Iterating over maps

To traverse a map, we use the for loop with range, just like with slices, but we get key and value.

menu := map[string]float64{
    "Coffee": 1.50,
    "Toast": 2.00,
    "Juice": 3.00,
}

for dish, price := range menu {
    fmt.Printf("%s costs %.2f\n", dish, price)
}
Copied!

Order is not guaranteed

This is critical. Maps in Go are not ordered. Every time you run the for range loop above, the elements might come out in a different order. Simply do not depend on that order.

If you need to iterate in order (for example, alphabetically), you must:

  1. Extract the keys into a slice.
  2. Sort that slice.
  3. Iterate over the slice and look up the map.

Maps and shared data

As with slices, when you pass a map to a function, a small descriptor is copied, but both point to the same internal table. If the function modifies it, the original map changes.

func modifyMap(m map[string]int) {
    m["new"] = 999
}

func main() {
    myMap := make(map[string]int)
    modifyMap(myMap)
    fmt.Println(myMap) // Prints map[new:999]
}
Copied!