We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.

How to Make Maps in Go

Lane Wagner
Lane WagnerBoot.dev co-founder and backend engineer

Last published

Table of Contents

Maps are similar to JavaScript objects, Python dictionaries, and Ruby hashes. Maps are a data structure that provides key->value mapping.

All the content from our Boot.dev courses are available for free here on the blog. This one is the "Maps" chapter of Learn Go. If you want to try the far more immersive version of the course, do check it out!

Creating Maps in Go

The zero value of a map is nil.

We can create a map by using the make() function:

ages := make(map[string]int)
ages["John"] = 37
ages["Mary"] = 24
ages["Mary"] = 21 // overwrites 24

Or by using a literal:

ages := map[string]int{
  "John": 37,
  "Mary": 21,
}

Map values can be structs too:

type car struct {
  registration string
  model        string
}

cars := map[string]car{
  "ABC-123": {registration: "ABC-123", model: "Civic"},
}

The len() function works on a map, it returns the total number of key/value pairs.

ages := map[string]int{
  "John": 37,
  "Mary": 21,
}
fmt.Println(len(ages)) // 2

var, new, and make

var varStyle map[int]int

var varStyle map[int]int creates a nil map. Writing (but not reading interestingly enough) will cause a panic. You probably don't want a nil map.

newStyle := new(map[string]int)

newStyle := new(map[string]int) creates a pointer to a nil map... very often not what you want.

makeStyle := make(map[string]int)

makeStyle := make(map[string]int) This is probably what you want! If you know your space requirements you can optimize for allocation by passing in a size:

// Give me a map with room for 10 items before needing to allocate more space
makeStyle := make(map[string]int, 10)

Mutating Maps

Insert an Element

m[key] = elem

Get an Element

elem = m[key]

Delete an Element

delete(m, key)

Check If a Key Exists

elem, ok := m[key]
  • If key is in m, then ok is true and elem is the value as expected.
  • If key is not in the map, then ok is false and elem is the zero value for the map's element type.

Effective Go on Maps

The following sections are paraphrased from Effective Go regarding maps.

Like Slices, Maps Hold References

Like slices, maps hold references to an underlying data structure. If you pass a map to a function that changes the contents of the map, the changes will be visible in the caller.

Map Literals

Maps can be constructed using the usual composite literal syntax with colon-separated key-value pairs, so it's easy to build them during initialization.

var timeZone = map[string]int{
    "UTC":  0*60*60,
    "EST": -5*60*60,
    "CST": -6*60*60,
    "MST": -7*60*60,
    "PST": -8*60*60,
}

Missing Keys

An attempt to fetch a map value with a key that is not present in the map will return the zero value for the type of the entries in the map. For instance, if the map contains integers, looking up a non-existent key will return 0. A set can be implemented as a map with value type bool. Set the map entry to true to put the value in the set, and then test it by simple indexing.

attended := map[string]bool{
    "Ann": true,
    "Joe": true,
    ...
}

if attended[person] { // will be false if person is not in the map
    fmt.Println(person, "was at the meeting")
}

Sometimes you need to distinguish a missing entry from a zero value. Is there an entry for "UTC" or is that 0 because it's not in the map at all? You can discriminate with a form of multiple assignment.

var seconds int
var ok bool
seconds, ok = timeZone[tz]

For obvious reasons, this is called the "comma ok" idiom. In this example, if tz is present, seconds will be set appropriately and ok will be true; if not, seconds will be set to zero and ok will be false. Here's a function that puts it together with a nice error report:

func offset(tz string) int {
    if seconds, ok := timeZone[tz]; ok {
        return seconds
    }
    log.Println("unknown time zone:", tz)
    return 0
}

Now we can redo the first example of a set by using a more efficient map with empty structs, which don't take up any space in memory:

attended := map[string]struct{}{
    "Ann": {},
    "Joe": {},
    ...
}

if _, ok := attended[person]; ok {
    fmt.Println(person, "was at the meeting")
}

Deleting Map Entries

To delete a map entry, use the built-in delete function, whose arguments are the map and the key to be deleted. It's safe to do this even if the key is already absent from the map.

delete(timeZone, "PDT")  // Now on Standard Time

Combining a Map Lookup With an if Statement

You can check if a key is already present in a map by using the second return value from the index operation.

You can combine an if statement with an assignment operation to use the variables inside the if block:

names := map[string]int{}
missingNames := []string{}

if _, ok := names["Denna"]; !ok {
    // if the key doesn't exist yet,
    // append the name to the missingNames slice
    missingNames = append(missingNames, "Denna")
}

Nested Maps and Struct Keys

Maps can contain maps, creating a nested structure. For example:

map[string]map[string]int
map[rune]map[string]int
map[int]map[string]map[string]int

Any type can be used as the value in a map, but keys are more restrictive.

The following explanation and examples are from the official Go blog.

Map keys may be of any type that is comparable. The language spec defines this precisely, but in short, comparable types are boolean, numeric, string, pointer, channel, and interface types, and structs or arrays that contain only those types. Notably absent from the list are slices, maps, and functions; these types cannot be compared using ==, and may not be used as map keys.

It's obvious that strings, ints, and other basic types should be available as map keys, but perhaps unexpected are struct keys. Struct can be used to key data by multiple dimensions. For example, this map of maps could be used to tally web page hits by country:

hits := make(map[string]map[string]int)

This is a map of string to (map of string to int). Each key of the outer map is the path to a web page with its own inner map. Each inner map key is a two-letter country code. This expression retrieves the number of times an Australian has loaded the documentation page:

n := hits["/doc/"]["au"]

Unfortunately, this approach becomes unwieldy when adding data, as for any given outer key you must check if the inner map exists, and create it if needed:

func add(m map[string]map[string]int, path, country string) {
    mm, ok := m[path]
    if !ok {
        mm = make(map[string]int)
        m[path] = mm
    }
    mm[country]++
}
add(hits, "/doc/", "au")

On the other hand, a design that uses a single map with a struct key does away with all that complexity:

type Key struct {
    Path, Country string
}
hits := make(map[Key]int)

When a Vietnamese person visits the home page, incrementing (and possibly creating) the appropriate counter is a one-liner:

hits[Key{"/", "vn"}]++

And it's similarly straightforward to see how many Swiss people have read the spec:

n := hits[Key{"/ref/spec", "ch"}]

Counting Distinct Words With Empty Structs

Say you need to count the number of distinct words in a document. Since all that matters is counting distinct words, we don't care about the value of each key in the map. You can use struct{}{} as the value of your map key. This empty struct uses no memory. For example, both map[string]bool and map[string]struct{} can track unique words, but they use different amounts of memory:

distinctWordsBool := make(map[string]bool)
distinctWordsStruct := make(map[string]struct{})

distinctWordsBool["hello"] = true         // Uses 1 byte for the bool value
distinctWordsStruct["hello"] = struct{}{} // Uses 0 bytes for the empty struct

Frequently Asked Questions

What is the zero value of a map in Go?

The zero value of a map is nil. Writing to a nil map will cause a panic, but reading from it will not.

How can you create a map in Go?

You can create a map by using the make function or by using a literal. Map literals use colon-separated key-value pairs, so they are easy to build during initialization.

What happens when a map key is missing?

Fetching a map value with a key that is not present returns the zero value for the map's element type.

How do you check whether a key exists in a Go map?

Use elem, ok := m[key]. If the key is in the map, ok is true and elem is the value; otherwise, ok is false and elem is the zero value for the map's element type.

What types can be used as Go map keys?

Map keys may be of any type that is comparable. Slices, maps, and functions cannot be compared using == and may not be used as map keys.

Related Articles