06. Maps in Go
🗺️ Unlock the power of Go's maps! This guide dives deep into map basics, operations, key existence checks, and more, equipping you with the knowledge to efficiently manage data structures in your Go projects. 🚀
What we will learn in this post?
- 👉 Map Basics
- 👉 Map Operations
- 👉 Checking Key Existence
- 👉 Iterating Over Maps
- 👉 Map Zero Value and Nil Maps
- 👉 Maps as Reference Types
- 👉 Conclusion!
Maps in Go 🗺️
Go maps are like dictionaries or hash tables in other languages. They store data in key-value pairs. Each key is unique, and it maps to a specific value. Think of it as a phone book, where names (keys) map to phone numbers (values).
Declaring and Initializing Maps 🛠️
You can create a map using make(map[K]V) or with a map literal:
1
2
3
4
5
// Using make
myMap := make(map[string]int) // Key: string, Value: int
// Using a map literal
anotherMap := map[string]string{"name": "Alice", "city": "Wonderland"}
Key and Value Types:
- Keys (K) must be comparable types (e.g.,
string,int,bool). - Values (V) can be any type.
Using Maps 🚀
Here’s how to use a map:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import "fmt"
func main() {
// Create a map
studentGrades := make(map[string]int)
// Add key-value pairs
studentGrades["Alice"] = 90
studentGrades["Bob"] = 80
studentGrades["Charlie"] = 70
// Access a value
fmt.Println("Alice's grade:", studentGrades["Alice"]) // Output: Alice's grade: 90
// Check if a key exists
grade, ok := studentGrades["David"] // ok is true if "David" exists
if ok {
fmt.Println("David's grade:", grade)
} else {
fmt.Println("David's grade not found") // Output: David's grade not found
}
// Delete a key-value pair
delete(studentGrades, "Bob")
}
Want to learn more? Check out the official Go documentation on maps!
Below is a compact flow diagram that summarizes common map operations (create, add/update, retrieve, delete, and existence check) — useful as a quick reference when designing small caches or lookup tables in your code.
graph LR
OP_CREATE["Create map"]:::javaStyle --> OP_ADD["Add / Update"]:::jdbcStyle
OP_ADD --> OP_RETRIEVE["Retrieve (key)"]:::driverStyle
OP_RETRIEVE --> OP_CHECK{"Exists?"}:::dbStyle
OP_CHECK -- Yes --> OP_USE["Use value"]:::useStyle
OP_CHECK -- No --> OP_HANDLE["Handle missing / default"]:::useStyle
OP_USE --> OP_DELETE["Delete"]:::jdbcStyle
classDef javaStyle fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef jdbcStyle fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef driverStyle fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef dbStyle fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef useStyle fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
linkStyle default stroke:#e67e22,stroke-width:3px;
Working with Key-Value Pairs 🔑
Let’s learn how to manage data using key-value pairs, kind of like a digital dictionary!
Adding New Entries ➕
To add a new entry, imagine giving it a unique key and then assigning a value to it. For example:
1
2
3
4
5
6
7
8
9
10
11
12
package main
import "fmt"
func main() {
myDict := make(map[string]interface{})
myDict["name"] = "Alice"
myDict["age"] = 30
myDict["is_student"] = false
fmt.Println(myDict) // map[name:Alice age:30 is_student:false]
}
Below is the equivalent practical example in Go, demonstrating creation, add/update, retrieve and delete operations on a map — useful in command-line tools or simple data caches.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import "fmt"
func main() {
cache := make(map[string]string)
// Add / update
cache["user:1001"] = "alice@example.com"
cache["user:1002"] = "bob@example.com"
// Retrieve
if v, ok := cache["user:1001"]; ok {
fmt.Println("Found:", v)
} else {
fmt.Println("Not found")
}
// Delete
delete(cache, "user:1002")
// Iterate and print
for k, v := range cache {
fmt.Printf("%s -> %s\n", k, v)
}
}
Retrieving Values 🔎
To get a value, just ask for it by its key:
1
2
3
4
5
6
7
8
9
package main
import "fmt"
func main() {
m := map[string]string{"name": "Alice"}
name := m["name"]
fmt.Println(name) // Output: Alice
}
Remember: Keys must be unique.
Updating Values 🔄
Want to change a value? Simply assign a new value to the existing key:
1
2
3
4
5
6
7
8
9
package main
import "fmt"
func main() {
m := map[string]int{"age": 30}
m["age"] = 31
fmt.Println(m["age"]) // Output: 31
}
Deleting Entries 🗑️
Use del to remove a key-value pair:
1
2
3
4
5
6
7
8
9
package main
import "fmt"
func main() {
m := map[string]interface{}{"name": "Alice", "age": 31, "is_student": true}
delete(m, "is_student")
fmt.Println(m) // Output: map[name:Alice age:31]
}
- Key Points:
- Keys are unique identifiers.
- Values can be any data type (string, number, boolean, etc.)
delremoves the entire key-value pair.
The Comma-Ok Idiom in Go: Checking Map Keys 🔑
Go’s map data structure doesn’t throw an error when you try to access a key that doesn’t exist. Instead, it returns the zero value for the map’s value type. This can be confusing, so Go provides a neat trick called the “comma-ok idiom” to check if a key is actually present.
How it Works 🤔
The syntax looks like this: value, ok := myMap[key].
value: This will hold the value associated with the key, or the zero value if the key is missing.ok: This is a boolean variable. It will betrueif the key exists in the map, andfalseif it doesn’t.
Missing Key vs. Zero Value 🧐
It’s important to understand the difference:
- Missing Key: The key literally isn’t in the map.
okwill befalse. - Zero Value: The key is in the map, but its associated value is the zero value for that type (e.g.,
0forint,""forstring,falseforbool).okwill betrue.
1
2
3
4
5
6
7
8
9
myMap := map[string]int{"age": 30, "count": 0}
age, ok := myMap["age"] // age = 30, ok = true
city, ok := myMap["city"] // city = 0, ok = false (key doesn't exist)
count, ok := myMap["count"] // count = 0, ok = true (key exists, value is 0)
fmt.Println(age, ok)
fmt.Println(city, ok)
fmt.Println(count,ok)
This example shows that city returns the zero value for an int, but ok is false when it doesn’t exists, also count which is initialized to 0 has the ok set to true.
Here is a helpful resource to learn more about checking for a Key Existence: Checking Key Existence in Maps | Go by Example
This small diagram shows the comma-ok decision flow when you access a key: check existence with the comma-ok idiom, then branch to using the value or handling a missing key. It’s a handy visual for teaching or quick reference.
graph LR
ACC["Access myMap[key]"]:::javaStyle --> DEC{"key present? (ok)"}:::driverStyle
DEC -- true --> USE["Use value"]:::dbStyle
DEC -- false --> ZERO["Zero value / handle missing"]:::jdbcStyle
classDef javaStyle fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef jdbcStyle fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef driverStyle fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef dbStyle fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef useStyle fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
linkStyle default stroke:#e67e22,stroke-width:3px;
Iterating Through Maps in Go with range 🗺️
Go’s range keyword makes looping through maps super easy! The important thing to remember is that the iteration order is not guaranteed. It can be random each time you run your program. Let’s explore how it works.
Key-Only Iteration 🔑
You can get only the keys of the map using for key := range myMap.
1
2
3
4
5
6
7
8
9
10
11
12
package main
import "fmt"
func main() {
myMap := map[string]int{"a": 1, "b": 2, "c": 3}
fmt.Println("Iterating over keys:")
for key := range myMap {
fmt.Println("Key:", key)
}
}
Key-Value Iteration 🔑➡️Value
To access both keys and values, use for key, value := range myMap.
1
2
3
4
5
6
7
8
9
10
11
12
package main
import "fmt"
func main() {
myMap := map[string]int{"a": 1, "b": 2, "c": 3}
fmt.Println("Iterating over keys and values:")
for key, value := range myMap {
fmt.Printf("Key: %s, Value: %d\n", key, value)
}
}
rangeprovides a simple way to iterate.- Remember, the order isn’t predictable!
- Use
key := range mapfor keys only. - Use
key, value := range mapfor both keys and values.
For more detailed information on the usage of maps, please see this resource.
When iterating over maps, the order is undefined; if you need deterministic output (for tests or stable UI), convert keys to a slice, sort them, and iterate. The diagram below shows that pattern in a simple flow.
graph LR
START["Start: map variable"]:::javaStyle --> RANGE["for range over map"]:::driverStyle
RANGE --> UNORDERED["Iteration order: undefined"]:::dbStyle
UNORDERED --> TO_SLICE["Collect keys to slice"]:::jdbcStyle
TO_SLICE --> SORT["Sort slice"]:::useStyle
SORT --> ITER["Iterate deterministically"]:::driverStyle
classDef javaStyle fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef jdbcStyle fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef driverStyle fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef dbStyle fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef useStyle fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
linkStyle default stroke:#e67e22,stroke-width:3px;
Understanding Go Maps and Nil Values 🗺️
Let’s explore maps in Go and how they handle nil values! Maps are like dictionaries, storing key-value pairs.
Nil Maps vs. Empty Maps 🧐
A nil map is a map that hasn’t been initialized. Think of it as a map variable that’s declared, but no memory has been allocated. An empty map, on the other hand, is a map that has been initialized using make(), but it currently contains no key-value pairs.
1
2
var nilMap map[string]int // nil map
emptyMap := make(map[string]int) // empty map
Here’s a basic flowchart to help visualise the difference.
graph LR
MV["Map Variable"]:::javaStyle --> INIT{"Initialized?"}:::driverStyle
INIT -- Yes --> EMPTY["Empty Map (make())"]:::dbStyle
INIT -- No --> NIL["Nil Map"]:::jdbcStyle
classDef javaStyle fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef jdbcStyle fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef driverStyle fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef dbStyle fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef useStyle fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
class MV javaStyle;
class INIT driverStyle;
class EMPTY dbStyle;
class NIL jdbcStyle;
linkStyle default stroke:#e67e22,stroke-width:3px;
Working with Nil Maps - What’s Allowed (and Not!) ⛔️
You can read from a nil map without causing a panic. If you try to access a key in a nil map, you’ll simply get the zero value of the map’s value type. However, you cannot write (add or update elements) to a nil map! Doing so will cause a panic.
- Reading from a
nilmap: OK 👍. Returns the zero value of the value type. - Writing to a
nilmap: PANIC 💥! You must initialize the map withmake()first.
Example:
1
2
3
4
5
6
7
var myMap map[string]string
name := myMap["name"] //This works fine, name will be an empty string
//myMap["city"] = "London" //This will cause a panic.
myMap = make(map[string]string)
myMap["city"] = "London" //This is perfectly fine!
Resource Link: For more detailed information, check out the official Go documentation on maps.
Maps in Go: Reference Types 🗺️
Go maps are reference types. This means a map variable doesn’t directly store the data. Instead, it holds a pointer to the underlying data structure where key-value pairs are stored. This has important implications when you pass maps to functions.
Passing Maps to Functions ➡️
Because maps are reference types, when you pass a map to a function, you’re passing a copy of the pointer, not the map’s data itself. Therefore, if the function modifies the map (adds, deletes, or updates elements), those changes will be visible to the caller!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import "fmt"
func modifyMap(m map[string]int) {
m["age"] = 30 // Modifying the map
}
func main() {
myMap := map[string]int{"name": 25}
fmt.Println("Before:", myMap) // Output: Before: map[name:25]
modifyMap(myMap)
fmt.Println("After:", myMap) // Output: After: map[age:30 name:25]
}
Reference Types vs. Value Types 🆚
This is different from value types (like int, string, struct). When you pass a value type to a function, you’re passing a copy of the value. Modifications to the copy don’t affect the original.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import "fmt"
func modifyInt(x int) {
x = 10 // Modifying the copy of the integer
}
func main() {
myInt := 5
fmt.Println("Before:", myInt) // Output: Before: 5
modifyInt(myInt)
fmt.Println("After:", myInt) // Output: After: 5
}
Value types create complete copies, and changes to the copy do not impact the original. Reference types share the same underlying data structure.
Key Takeaway: Changes to a map passed to a function will affect the original map in the calling function because maps are passed by reference.
Conclusion
And that’s a wrap! 🎉 We’d love to hear what you think. Did you find this helpful? Any tips of your own to share? Drop a comment below – we’re all ears!👂 Let’s chat! 👇