Maps are a built-in data structure in Go that act as a collection of key-value pairs. They are also known as associative arrays, hash tables, or dictionaries in other programming languages.

In this post, we will learn about maps in Golang, and how to use them.

We will learn about the syntax, how to create a map, how to add and delete elements from a map, how to iterate over a map, and how to check if a key exists in a map.

banner

Creating a Map

When we want to create a new map, we need to declare its key and value types. The syntax for declaring a map type is:

var m map[keyType]valueType

This declares m as a map with keys of type keyType and values of type valueType.

Note that there are some restrictions on the types that can be used as keys. However, any type can be used as a value.

This only declares the type, but to create an actual map, we need to use the built-in make function.

For example, if we want to create a map with string keys and int values, we can do so as follows:

myMap := make(map[string]int)

This creates an empty map with string keys and int values.

Adding Key-Value Pairs to a Map

We can add key-value pairs to a map using the following syntax:

m[key] = value

This adds a key-value pair to the map m. If the key already exists in the map, then the value is updated. If the key does not exist, then a new key-value pair is added to the map.

For example, if we want to add the key-value pair "the_answer": 42 to the map myMap, we can do so as follows:

myMap["the_answer"] = 42

If you set a key that already exists, then it’s previous value is overwritten.

Getting a Value from a Map

We can get the value associated with a key from a map using the following syntax:

value = m[key]

This returns the value associated with the key key in the map m. If the key does not exist in the map, then the zero value of the value type is returned.

Let’s see an example of this. If we want to get the value associated with the key "the_answer" from the map myMap, we can do so as follows:

value := myMap["the_answer"]

However, if we try to get the value associated with the key "the_question" from the map myMap, we will get the zero value of the value type, which is 0 in this case.

value := myMap["the_question"]
// value == 0

Checking if a Key Exists in a Map

Since we get a zero value when a key doesn’t exist in a map, we can’t use this to determine if the key was present in the map or not. (What would we do if the key was intentionally set to the zero value?)

Fortunately, when obtaining a value from a map, we can also get a boolean value that indicates whether the key was present in the map or not:

value, ok := m[key]

The value of the second variable ok is true if the key was present in the map, and false otherwise.

Deleting Key-Value Pairs

We can delete a key-value pair from a map using the inbuilt delete function:

delete(m, key)

For example, if we want to delete the key-value pair "the_answer": 42 from the map myMap, we can do so as follows:

delete(myMap, "the_answer")

The delete function works even if the key doesn’t exist in the map, or even if the map is nil. In that case, it does nothing, and acts as a no-op.

Iterating Over a Map

We can iterate over a map using the range keyword. In each iteration, we can access a the key and value variables:

for key, value := range m {
    // do something with key and value
}

Note that the order of iteration is not guaranteed (even for the same map in two different iterations). If you want to iterate over the map in a specific order, you will need to sort the keys first, or else maintain a separate slice of keys in the order you want.

Allowed Types for Keys

There are some restrictions on the types that can be used as keys in a map. The key type must be one of the following:

  1. Any type that is comparable using the == operator. This includes all the basic types, such as int, string, bool, etc. and also pointers, channels and interfaces.
  2. Any struct or array type that contains only types that are comparable using the == operator.

Slices, functions, and maps cannot be used as keys, since they are not comparable using the == operator.

Performance of Various Map Operations

After running some benchmarks on my machine, I found the following results:

OperationTime Taken
Map Initialization3.874 ns/op
Add Key-Value Pair (to an empty map)5.339 ns/op
Get Value6.99 ns/op
Update Value10.486 ns/op
Delete Key-Value Pair12.71 ns/op
Add Key-Value Pair (to a large map)123 ns/op
Get Value (to a large map)7.6 ns/op
Update Value (to a large map)8.8 ns/op
Delete Key-Value Pair (to a large map)7.9 ns/op
How we got these results

The complete code for the benchmarks is available on Github. When running this on a laptop, I got the following results:

=== RUN   BenchmarkMapInit
BenchmarkMapInit
BenchmarkMapInit-8      310994732                3.874 ns/op           0 B/op          0 allocs/op
=== RUN   BenchmarkMapAdd
BenchmarkMapAdd
BenchmarkMapAdd-8       130387846                9.213 ns/op           0 B/op          0 allocs/op
=== RUN   BenchmarkMapGet
BenchmarkMapGet
BenchmarkMapGet-8       100000000               10.86 ns/op            0 B/op          0 allocs/op
=== RUN   BenchmarkMapUpdate
BenchmarkMapUpdate
BenchmarkMapUpdate-8    82914201                14.36 ns/op            0 B/op          0 allocs/op
=== RUN   BenchmarkMapDelete
BenchmarkMapDelete
BenchmarkMapDelete-8    71693688                16.58 ns/op            0 B/op          0 allocs/op
=== RUN   BenchmarkLargeMapGet
BenchmarkLargeMapGet
BenchmarkLargeMapGet-8          156739308                7.662 ns/op           0 B/op          0 allocs/op
=== RUN   BenchmarkLargeMapAdd
BenchmarkLargeMapAdd
BenchmarkLargeMapAdd-8          11410676               123.3 ns/op            63 B/op          0 allocs/op
=== RUN   BenchmarkLargeMapUpdate
BenchmarkLargeMapUpdate
BenchmarkLargeMapUpdate-8       131575159                8.814 ns/op           0 B/op          0 allocs/op
=== RUN   BenchmarkLargeMapDelete
BenchmarkLargeMapDelete
BenchmarkLargeMapDelete-8       153680978                7.911 ns/op           0 B/op          0 allocs/op

Now, each of the benchmarks for map operations involved a map initialization. For example, the benchmark for adding a key-value pair to a map involved initializing a map and then adding a key-value pair to it:

func BenchmarkMapAdd(b *testing.B) {
	for i := 0; i < b.N; i++ {
		m := make(map[int]int)
		m[i] = i
	}
}

So to get the final results, we need to subtract the time taken for map initialization from the time taken for the operation. For example, the time taken for adding a key-value pair to a map is 9.213 - 3.874 = 5.339 ns/op.

For large map operations, the map was initialized outside the test loop, so we don’t subtract the initialization time.


Note that the actual numbers are not important here. What is important is the relative performance of the various operations. For example, we can see that getting a value from a map doesn’t become that much slower even for a large map, but adding a key-value pair to a large map is much slower than adding a key-value pair to an empty map.