Introduction to Maps
A map is a built in Go data structure that stores information as key value pairs.
Unlike arrays and slices, where elements are accessed using their positions (indexes), values in a map are accessed using their keys.
Every entry in a Go map consists of two things:
- Key: An identifier used to find an entry in the map.
- Value: The information associated with that key.
For example, imagine storing information about a user. We could use the user's name and email as key value pairs.
user := map[string]string{ "name": "Adetola", "email": "adetola@yopmail.com", } syntax
The general syntax for declaring a map is:
map[keyType]valueType In our example:
map[string]string -
maptells Go that we are declaring a map. - The first
stringspecifies the data type of the keys. - The second
stringspecifies the data type of the values.
This means we have a map where both the keys and values are strings.
Can keys and values have different data types?
Yes. The key and value types do not have to be the same.
ages := map[string]int{ "Adetola": 25, "Tobi": 30, } Here, the keys are strings, while the values are integers.
Other possible combinations include:
map[int]string map[string]bool map[string]float64 However, all keys in a particular map must have the same type, and all values must have the same type.
Key takeaway: A Go map stores data in key-value pairs. Unlike arrays and slices, we use keys rather than indexes to access its values. The key and value types can be different, but each must remain consistent throughout the map.
Declaring and Initialising Maps
There are several ways to create a map in Go.
1. Using a map literal
A map literal is useful when you already know the entries you want to store. The map is created and populated with values immediately.
userAges := map[string]int{ "Ade": 25, "Tola": 30, } Here:
-
userAgesis the variable name. -
map[string]intdeclares a map with string keys and integer values. -
"Ade"and"Tola"are the keys. -
25and30are their corresponding values.
A map literal allows us to create and populate a map in one step.
2. Using make()
The built-in make() function is used when you want to create an empty map and add entries later.
userAges := make(map[string]int) This creates an initialized, empty map that is ready to receive entries.
userAges := make(map[string]int) userAges["Ade"] = 25 userAges["Tola"] = 30 fmt.Println(userAges) Output:
map[Ade:25 Tola:30] You can also provide an optional initial capacity hint:
userAges := make(map[string]int, 10) This tells Go that you expect the map to hold approximately 10 entries. It does not limit the map to 10 entries.
3. Declaring a map without initialising it
You can declare a map without initialising it.
var userAges map[string]int This creates a nil map. A nil map can be read from, and its length can be checked, but you cannot add entries to it.
var userAges map[string]int fmt.Println(userAges["Ade"]) // 0 fmt.Println(len(userAges)) // 0 However, attempting to add an entry causes a runtime panic.
var userAges map[string]int userAges["Ade"] = 25 // Runtime panic Why?
Because the map has been declared but not initialised. It does not have an underlying map structure ready to store entries. To add entries, initialise it first using either a map literal or make().
Key distinction: var userAges map[string]int creates a nil map, while make(map[string]int) creates an initialized, empty map.
Both initially have zero entries, but only the initialized map can accept new entries.
Adding and Updating Values in a Map
Go uses the same syntax to add a new entry and update an existing entry.
The general syntax is:
mapName[key] = value 1. Adding a new entry
When the key does not already exist, Go creates a new entry.
userAges := make(map[string]int) userAges["Ade"] = 25 userAges["Tola"] = 30 fmt.Println(userAges) Output:
map[Ade:25 Tola:30] The first assignment creates an entry with the key "Ade" and value 25 and the second assignment creates another entry with the key "Tola" and value 30.
2. Updating an existing entry
When the key already exists, assigning a new value updates the existing entry.
userAges := map[string]int{ "Ade": 25, "Tola": 30, } userAges["Ade"] = 26 fmt.Println(userAges) Output:
map[Ade:26 Tola:30] Notice that "Ade" originally had a value of 25. After the assignment, its value becomes 26.
Go does not create another "Ade" entry. It updates the existing one.
Key takeaway: The assignment syntax is the same for adding and updating. Go checks whether the key already exists and either creates a new entry or replaces the existing value.
Accessing Values in a Map
To retrieve a value from a map, you use its key. The syntax is:
mapName[key] userAges := map[string]int{ "Ade": 25, "Tola": 30, } fmt.Println(userAges["Ade"]) Output:
25 Go looks for the key "Ade" and returns the value associated with it.
What happens when a key does not exist?
If you try to access a key that does not exist, Go returns the zero value of the map's value type.
For example:
userAges := map[string]int{ "Ade": 25, } fmt.Println(userAges["Tola"]) Output:
0 Because the value type is int, Go returns the integer zero.
Other examples:
| Map type | Zero value for a missing key |
|---|---|
map[string]int | 0 |
map[string]string | "" |
map[string]bool | false |
map[string]float64 | 0.0 |
However, this creates a problem like what if a key does not exist, but another key exists with an actual value of 0?, How do we distinguish between the two?
Go solves this using the comma-ok idiom.
Checking Whether a Key Exists (Comma-OK Idiom)
Go allows you to retrieve a value and check whether its key exists at the same time.
Example:
userAges := map[string]int{ "Ade": 25, "Tola": 30, } age, exists := userAges["Ade"] if exists { fmt.Println("Age:", age) } else { fmt.Println("User not found") } Output:
Age: 25 Let's break this down:
age, exists := userAges["Ade"] Go returns two values:
-
age: The value associated with the key. -
exists: A boolean indicating whether the key exists in the map.
If the key exists, exists is true.
If the key does not exist, exists is false.
Let's look at another example:
age, exists := userAges["Femi"] if exists { fmt.Println("Age:", age) } else { fmt.Println("User not found") } Output:
User not found Even though Go returns the zero value for a missing key, the exists variable tells us that the key was not found.
This is particularly useful when working with user records, configuration, permissions, and other data where knowing whether an entry exists matters.
It is called the comma-ok idiom because of the two-value assignment used to check the result.
Deleting Entries from a Map
Go has a built-in function called delete() for removing entries from a map.
Syntax:
delete(mapName, key) userAges := map[string]int{ "Ade": 25, "Tola": 30, "Femi": 28, } delete(userAges, "Tola") fmt.Println(userAges) Output:
map[Ade:25 Femi:28] The delete() function removes the entry associated with the specified key.
A few things to remember:
- Deleting an existing key removes its entry.
- Deleting a key that does not exist does nothing.
-
delete()does not return a value. - Calling
delete()on a nil map is safe and does nothing.
Getting the Number of Entries in a Map
You can use the built-in len() function to find out how many entries a map contains.
Example:
userAges := map[string]int{ "Ade": 26, "Tola": 30, "Femi": 28, } fmt.Println(len(userAges)) Output:
3 The len() function returns the number of key-value pairs currently stored in the map.
Unlike an array, a map does not have a fixed number of entries. Its length changes as you add or delete entries.
For example:
userAges := make(map[string]int) fmt.Println(len(userAges)) // 0 userAges["Ade"] = 26 userAges["Tola"] = 30 fmt.Println(len(userAges)) // 2 delete(userAges, "Ade") fmt.Println(len(userAges)) // 1 Iterating Over a Map
Sometimes you don't want to retrieve just one value. You want to go through every entry in a map.
Go uses the range keyword to iterate over maps.
userAges := map[string]int{ "Ade": 25, "Tola": 30, "Femi": 28, } for name, age := range userAges { fmt.Println(name, age) } Possible output:
Ade 25 Tola 30 Femi 28 The output order may be different when you run the program.
for name, age := range userAges { -
forstarts the loop. -
rangeallows us to iterate over the map. -
userAgesis the map we want to iterate through. -
namereceives the key from each entry. -
agereceives the corresponding value.
For every entry in the map, Go assigns its key to name and its value to age.
The loop then executes the code inside its body.
fmt.Println(name, age) This prints the key and its corresponding value.
Important: Map iteration order
Go does not guarantee the order in which map entries are returned during iteration. This means you should not rely on a map to preserve the order in which entries were inserted.
If you need a specific order, you must collect the keys and sort them before iterating.
Important Characteristics of Go Maps
A. Map keys must be comparable
Go requires map keys to be types that can be compared using the == operator.
Common valid key types include:
- Strings
- Integers
- Floating-point numbers
- Booleans
- Pointers
- Arrays whose element types are comparable
- Structs whose fields are all comparable
Slices, maps, and functions cannot be used directly as map keys.
For example:
// Valid ages := map[string]int{} // Invalid // users := map[[]int]string{} Why? Because slices cannot be compared using == in Go.
This restriction exists because Go needs to be able to compare keys when storing and retrieving map entries.
B. Maps are reference like types
When you assign a map to another variable, Go does not create an independent copy of all its entries.
Both variables refer to the same underlying map data.
Example:
first := map[string]int{ "Adetola": 25, } second := first second["Adetola"] = 30 fmt.Println(first["Adetola"]) // 30 fmt.Println(second["Adetola"]) // 30 Notice that changing the value through second also affects what we see through first.
This is different from arrays, where assigning one array to another copies the entire array.
C. A map is not safe for concurrent writes by default
If multiple goroutines access a map concurrently and at least one of them writes to it, you need synchronization.
Ordinary Go maps are not automatically safe for concurrent writes.
We'll explore goroutines, concurrency, and synchronization later in your Go learning journey.
For now, remember that when multiple goroutines need to modify the same map, you must protect those operations appropriately.
Go Maps vs JavaScript Objects
Since you already know JavaScript, let's connect the concepts. In JavaScript, you might represent the same information using an object:
const userAges = { Adetola: 25, Tobi: 30, }; console.log(userAges.Adetola); In Go:
userAges := map[string]int{ "Adetola": 25, "Tobi": 30, } fmt.Println(userAges["Adetola"]) They can serve similar purposes, but they are not exactly the same.
| Feature | Go map | JavaScript object |
|---|---|---|
| Structure | Key-value pairs | Properties and values |
| Key types | One declared comparable type | Strings or symbols |
| Value types | One declared value type | Can hold different types |
| Access | users["Adetola"] | users.Adetola |
| Add/update | users["name"] = value | users.name = value |
| Delete | delete(users, "name") | delete users.name |
| Missing key | Returns zero value; comma-ok checks existence | Returns undefined |
| Iteration | for range | for...in, Object.keys() |
JavaScript also has a built-in Map
JavaScript's Map object is another useful comparison to Go maps.
const ages = new Map(); ages.set("Adetola", 25); ages.set("Tobi", 30); console.log(ages.get("Adetola")); JavaScript's Map supports keys of different types, while a Go map has one declared key type.
For example, JavaScript can do this:
const example = new Map(); example.set("name", "Adetola"); example.set(25, "age"); example.set(true, "active"); Go requires all keys in a particular map to have the same declared type.
Practical Example: User Lookup
Imagine you're building a backend service and need to look up a user's account balance using their user ID.
A map can help you associate each user ID with a corresponding balance.
balances := map[string]float64{ "USER-001": 15000.50, "USER-002": 25000.00, "USER-003": 8000.75, } balance, exists := balances["USER-001"] if exists { fmt.Println("Account balance:", balance) } else { fmt.Println("Account not found") } Output:
Account balance: 15000.5 This is a practical use of maps: you have an identifier and need to retrieve its associated information.
Notice that:
- The keys are strings because the user IDs are strings.
- The values are
float64because the balances contain decimal values. - The comma-ok idiom checks whether the requested user ID exists.
This is a simplified example. In production financial systems, monetary values generally require more careful representation than ordinary floating-point numbers.
Lesson Takeaways
These are the points worth keeping in your study notes.
- A map is a built-in Go data structure that stores data as key-value pairs.
- A map is declared using
map[keyType]valueType. - Keys and values can have different types, but each type must remain consistent within its map.
- Map keys must be comparable. Slices, maps, and functions cannot be used directly as keys.
- You can initialise a map using a map literal or
make(). - A nil map can be read from but cannot be used to add entries.
- Use
mapName[key] = valueto add or update an entry. - Use
mapName[key]to retrieve a value. - A missing key returns the zero value of the map's value type.
- Use
value, exists := mapName[key]to check whether a key exists. This is the comma-ok idiom. - Use
delete(mapName, key)to remove an entry. - Use
len(mapName)to get the number of entries. - Use
for rangeto iterate over a map. - Map iteration order is not guaranteed.
- Assigning a map to another variable does not create an independent copy; both variables refer to the same map data.
- Ordinary Go maps are not safe for concurrent writes without synchronization.
-
map[string]intmeans string keys and integer values. -
value, exists := myMap[key]lets you distinguish a missing key from a key containing a zero value. - Maps are useful when you need to retrieve information using an identifier rather than a numerical position.
![Data Structures in Go[Maps]](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7jlrbe3poeh1o6uhurbj.png)

