Post

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. 🚀

06. Maps in Go

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.)
    • del removes 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 be true if the key exists in the map, and false if 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. ok will be false.
  • Zero Value: The key is in the map, but its associated value is the zero value for that type (e.g., 0 for int, "" for string, false for bool). ok will be true.
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)
    }
}
  • range provides a simple way to iterate.
  • Remember, the order isn’t predictable!
  • Use key := range map for keys only.
  • Use key, value := range map for 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 nil map: OK 👍. Returns the zero value of the value type.
  • Writing to a nil map: PANIC 💥! You must initialize the map with make() 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! 👇

This post is licensed under CC BY 4.0 by the author.