Understanding Maps in Go
Written by Gopher Guides
Most modern programming languages have the concept of a dictionary or a hash type. These types are commonly used to store data in pairs with a key that maps to a value.
In Go, the map data type is what most programmers would think of as the dictionary type. It maps keys to values, making key-value pairs that are a useful way to store data in Go. A map is constructed by using the keyword map
followed by the key data type in square brackets [ ]
, followed by the value data type. The key-value pairs are then placed inside curly braces on either side { }:
map[key]value{}
You typically use maps in Go to hold related data, such as the information contained in an ID. A map with data looks like this:
map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
In addition to the curly braces, there are also colons throughout the map that connect the key-value pairs. The words to the left of the colons are the keys. Keys can be any comparable type in Go, like strings
, ints
, and so on.
The keys in the example map are:
"name"
"animal"
"color"
"location"
The words to the right of the colons are the values. Values can be any data type. The values in the example map are:
"Sammy"
"shark"
"blue"
"ocean"
Like the other data types, you can store the map inside a variable, and print it out:
sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
fmt.Println(sammy)
This would be your output:
Output
map[animal:shark color:blue location:ocean name:Sammy]
The order of the key-value pairs may have shifted. In Go, the map data type is unordered. Regardless of the order, the key-value pairs will remain intact, enabling you to access data based on their relational meaning.
Accessing Map Items
You can call the values of a map by referencing the related keys. Since maps offer key-value pairs for storing data, they can be important and useful items in your Go program.
If you want to isolate Sammy’s username, you can do so by calling sammy["name"]
; the variable holding your map and the related key. Let’s print that out:
fmt.Println(sammy["name"])
And receive the value as output:
Output
Sammy
Maps behave like a database; instead of calling an integer to get a particular index value as you would with a slice, you assign a value to a key and call that key to get its related value.
By invoking the key "name"
you receive the value of that key, which is "Sammy"
.
Similarly you can call the remaining values in the sammy
map using the same format:
fmt.Println(sammy["animal"])
// returns shark
fmt.Println(sammy["color"])
// returns blue
fmt.Println(sammy["location"])
// returns ocean
By making use of the key-value pairs in map data types, you can reference keys to retrieve values.
Keys and Values
Unlike some programming languages, Go does not have any convenience functions to list out the keys or values of a map. An example of this would be Python’s .keys()
method for dictionaries. It does, however, allow for iteration by using the range
operator:
for key, value := range sammy {
fmt.Printf("%q is the key for the value %q\n", key, value)
}
When ranging through a map in Go, it’ll return two values. The first value will be the key, and the second value will be the value. Go will create these variables with the correct data type. In this case, the map key was a string
so key
will also be a string. The value
is also a string:
Output
"animal" is the key for the value "shark"
"color" is the key for the value "blue"
"location" is the key for the value "ocean"
"name" is the key for the value "Sammy"
To get a list of just the keys, you can use the range operator again. You can declare just one variable to only access the keys:
keys := []string{}
for key := range sammy {
keys = append(keys, key)
}
fmt.Printf("%q", keys)
The program begins by declaring a slice to store your keys in.
The output will show only the keys of your map:
Output
["color" "location" "name" "animal"]
Again, the keys are not sorted. If you want to sort them, you use the sort.Strings
function from the sort
package:
sort.Strings(keys)
With this function, you’ll receive the following output:
Output
["animal" "color" "location" "name"]
You can use the same pattern to retrieve just the values in a map. In the next example, you pre-allocate the slice to avoid allocations, thus making the program more efficient:
sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
items := make([]string, len(sammy))
var i int
for _, v := range sammy {
items[i] = v
i++
}
fmt.Printf("%q", items)
First you declare a slice to store your keys in; since you know how many items you need, you can avoid potential memory allocations by defining the slice at the exact same size. You then declare your index variable. As you don’t want the key, you use the _
operator, when starting your loop, to ignore the key’s value. Your output would be the following:
Output
["ocean" "Sammy" "shark" "blue"]
To determine the number of items in a map, you can use the built-in len
function:
sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
fmt.Println(len(sammy))
The output displays the number of items in your map:
Output
4
Even though Go doesn’t ship with convenience functions to get keys and values, it only takes a few lines of code to retrieve the keys and values when needed.
Checking Existence
Maps in Go will return the zero value for the value type of the map when the requested key is missing. Because of this, you need an alternative way to differentiate a stored zero, versus a missing key.
Let’s look up a value in a map that you know doesn’t exist and look at the value returned:
counts := map[string]int{}
fmt.Println(counts["sammy"])
You’ll see the following output:
Output
0
Even though the key sammy
was not in the map, Go still returned the value of 0
. This is because the value data type is an int
, and because Go has a zero value for all variables, it returns the zero value of 0
.
In many cases, this is undesirable and would lead to a bug in your program. When looking up the value in a map, Go can return a second, optional value. This second value is a bool
and will be true
if the key was found, or false
if the key was not found. In Go, this is referred to as the ok
idiom. Even though you could name the variable that captures the second argument anything you want, in Go, you always name it ok
:
count, ok := counts["sammy"]
If the key sammy
exists in the counts
map, then ok
will be true
. Otherwise ok
will be false.
You can use the ok
variable to decide what to do in your program:
if ok {
fmt.Printf("Sammy has a count of %d\n", count)
} else {
fmt.Println("Sammy was not found")
}
This would result in the following output:
Output
Sammy was not found
In Go, you can combine variable declaration and conditional checking with an if/else block. This allows you to use a single statement for this check:
if count, ok := counts["sammy"]; ok {
fmt.Printf("Sammy has a count of %d\n", count)
} else {
fmt.Println("Sammy was not found")
}
When retrieving a value from a map in Go, it’s always good practice to check for its existence as well to avoid bugs in your program.
Modifying Maps
Maps are a mutable data structure, so you can modify them. Let’s look at adding and deleting map items in this section.
Adding and Changing Map Items
Without using a method or function, you can add key-value pairs to maps. You do this using the maps variable name, followed by the key value in square brackets [ ]
, and using the equal =
operator to set a new value:
map[key] = value
In practice, you can see this work by adding a key-value pair to a map called usernames
:
usernames := map[string]string{"Sammy": "sammy-shark", "Jamie": "mantisshrimp54"}
usernames["Drew"] = "squidly"
fmt.Println(usernames)
The output will display the new Drew:squidly
key-value pair in the map:
Output
map[Drew:squidly Jamie:mantisshrimp54 Sammy:sammy-shark]
Because maps are returned unordered, this pair may occur anywhere in the map output. If you use the usernames
map later in your program file, it will include the additional key-value pair.
You can also use this syntax for modifying the value assigned to a key. In this case, you reference an existing key and pass a different value to it.
Consider a map called followers
that tracks followers of users on a given network. The user "drew"
had a bump in followers today, so you need to update the integer value passed to the "drew"
key. You’ll use the Println()
function to check that the map was modified:
followers := map[string]int{"drew": 305, "mary": 428, "cindy": 918}
followers["drew"] = 342
fmt.Println(followers)
Your output will show the updated value for drew
:
Output
map[cindy:918 drew:342 mary:428]
You see that the number of followers jumped from the integer value of 305
to 342
.
You can use this method for adding key-value pairs to maps with user input. Let’s write a quick program called usernames.go
that runs on the command line and allows input from the user to add more names and associated usernames:
usernames.go
package main
import (
"fmt"
"strings"
)
func main() {
usernames := map[string]string{"Sammy": "sammy-shark", "Jamie": "mantisshrimp54"}
for {
fmt.Println("Enter a name:")
var name string
_, err := fmt.Scanln(&name)
if err != nil {
panic(err)
}
name = strings.TrimSpace(name)
if u, ok := usernames[name]; ok {
fmt.Printf("%q is the username of %q\n", u, name)
continue
}
fmt.Printf("I don't have %v's username, what is it?\n", name)
var username string
_, err = fmt.Scanln(&username)
if err != nil {
panic(err)
}
username = strings.TrimSpace(username)
usernames[name] = username
fmt.Println("Data updated.")
}
}
In usernames.go
you first define the original map. You then set up a loop to iterate over the names. You request your user to enter a name and declare a variable to store it in. Next, you check to see if you had an error; if so, the program will exit with a panic. Because Scanln
captures the entire input, including the carriage return, you need to remove any space from the input; you do this with the strings.TrimSpace
function.
The if
block checks whether the name is present in the map and prints feedback. If the name is present it then continues back to the top of the loop. If the name is not in the map, it provides feedback to the user and then will ask for a new username for the associated name. The program checks again to see if there is an error. With no error, it trims off the carriage return, assigns the username value to the name key, and then prints feedback that the data was updated.
Let’s run the program on the command line:
go run usernames.go
You’ll see the following output:
Output
Enter a name:
Sammy
"sammy-shark" is the username of "Sammy"
Enter a name:
Jesse
I don't have Jesse's username, what is it?
JOctopus
Data updated.
Enter a name:
When you’re done testing, press CTRL + C
to escape the program.
This shows how you can modify maps interactively. With this particular program, as soon as you exit the program with CTRL + C
you’ll lose all your data unless you implement a way to handle reading and writing files.
To summarize, you can add items to maps or modify values with the map[key] = value
syntax.
Deleting Map Items
Just as you can add key-value pairs and change values within the map data type, you can also delete items within a map.
To remove a key-value pair from a map, you can use the built-in function delete()
. The first argument is the map you are deleting from. The second argument is the key you are deleting:
delete(map, key)
Let’s define a map of permissions:
permissions := map[int]string{1: "read", 2: "write", 4: "delete", 8: "create", 16:"modify"}
You no longer need the modify
permission, so you’ll remove it from your map. Then you’ll print out the map to confirm it was removed:
permissions := map[int]string{1: "read", 2: "write", 4: "delete", 8: "create", 16: "modify"}
delete(permissions, 16)
fmt.Println(permissions)
The output will confirm the deletion:
Output
map[1:read 2:write 4:delete 8:create]
The line delete(permissions, 16)
removes the key-value pair 16:"modify"
from the permissions
map.
If you would like to clear a map of all of its values, you can do so by setting it equal to an empty map of the same type. This will create a new empty map to use, and the old map will be cleared from memory by the garbage collector.
Let’s remove all the items within the permissions
map:
permissions = map[int]string{}
fmt.Println(permissions)
The output shows that you now have an empty map devoid of key-value pairs:
Output
map[]
Because maps are mutable data types, they can be added to, modified, and have items removed and cleared.
Conclusion
This tutorial explored the map data structure in Go. Maps are made up of key-value pairs and provide a way to store data without relying on indexing. This allows us to retrieve values based on their meaning and relation to other data types.