Welcome To Golang By Example

Maps in Go (Golang)

This is the  chapter 19 of the golang comprehensive tutorial series. Refer to this link for other chapters of the series – Golang Comprehensive Tutorial Series

Next Tutorial – Method
Previous Tutorial – Slice

Now let’s check out the current tutorial. Below is the table of contents for current tutorial.

Overview

Maps are golang builtin datatype similar to the hash table which maps a key to a value. Map is an unordered collection where each key is unique while values can be the same for two or more different keys. The advantages of using a map are that it provides fast retrieval, search, insert, and delete operations.

Maps are referenced data types. When you assign one map to another both refer to the same underlying map. Below is the format for a map

map[key_type]value_type

Both key_type and value_type can be of different type or same type. For below example the key type is string and value type is int.

map[string]int

Allowed Key types in a Map

The map key can be any type that is comparable. Some of the comparable types as defined by go specification are

Some of the types which are not comparable as per go specification and which cannot be used as a key in a map are.

Reference – https://golang.org/ref/spec#Comparison_operators

Allowed Value types in a Map

Value can be of any type in a map.

Creating a Map

Let’s look at each of above method one by one.

Using the map[<key_type>]<value_type> format

One of the most common way of creating a map is using the map literal:

map[key_type]value_type{}

An example of above where key type is string and value type is integer

employeeSalary := map[string]int{}

A map can also be created with some key values initialized

employeeSalary := map[string]int{
"John": 1000
"Sam": 2000
}

A new key-value pair can also be added to the map

employeeSalary["Tom"] = 2000

Let’s see a program

package main

import "fmt"

func main() {
    //Declare
    employeeSalary := map[string]int{}
    fmt.Println(employeeSalary)
    
    //Intialize using map lieteral
    employeeSalary = map[string]int{
        "John": 1000,
        "Sam":  1200,
    }

    //Adding a key value
    employeeSalary["Tom"] = 2000
    fmt.Println(employeeSalary)
}

Output

map[]
map[John:1000 Sam:1200 Tom:2000]

In the above program, we created a map literal intialized with some values. Then we added another key-value pair in it. Then we printed it using fmt.Println which prints all the key-value pairs in format map[key:value key:value]

A map can also be declared with var keyword, but it creates a nil map as default zero value of map is nil. Adding any key value pair to that map will cause a panic. Let’s see an example for this

package main

func main() {
    var employeeSalary map[string]int
    employeeSalary["Tom"] = 2000
}

Output

panic: assignment to entry in nil map

Above program run into panic as the map is nil.

One use case of having a map declared with var keyword is when an already existing map needs to be assigned to it or when we want to assign the result of a function.

Using Make

This is another way of creating the map. The builtin function make can be used to create a map. It returns an initialized map. Hence key-value pairs can be added to it.

package main

import "fmt"

func main() {
    //Declare
    employeeSalary := make(map[string]int)
    //Adding a key value
    employeeSalary["Tom"] = 2000
    fmt.Println(employeeSalary)
}

Output

map[Tom:2000]

In above program we created a map using make function. Then we added a key value pair in it. Then we printed it using fmt.Println which prints all the key value pairs.

Map Operations

The below operations are applicable for map

Add a key value pair

Below is the format for adding a key value pair to a map

mapName[key] = value

Let’s see an example

package main

import "fmt"

func main() {
    //Declare
    employeeSalary := make(map[string]int)

    //Adding a key value
    employeeSalary["Tom"] = 2000
    fmt.Pr

Output

map[Tom:2000]

Also note that adding to a nil map will cause a panic.

Update a key-value pair

When trying to add a key to the map which already exists, the new value will override the old value. This is analogous to updating a key in the map. Let’s see an example

package main

import "fmt"

func main() {
    //Declare
    employeeSalary := make(map[string]int)

    //Adding a key value
    fmt.Println("Before update")
    employeeSalary["Tom"] = 2000
    fmt.Println(employeeSalary)

    fmt.Println("After update")
    employeeSalary["Tom"] = 3000
    fmt.Println(employeeSalary)
}

Output

Before update
map[Tom:2000]
After update
map[Tom:3000]

In the above program after writing the same key “Tom” with a new value of 3000 it overwrites the existing value of 2000. When we print the map again the value printed is 3000

Get the value corresponding to a key

Below is the format for retrieving a value corresponding to a key

val := mapName[key]

Let’s see a program

package main

import "fmt"

func main() {
    //Declare
    employeeSalary := make(map[string]int)

    //Adding a key value
    employeeSalary["Tom"] = 2000
    
    //Retrieve a value 
    salary := employeeSalary["Tom"]
    fmt.Printf("Salary: %d", salary)
}

Delete a key value pair

Below is the format for delete a value corresponding to a key

delete(map_name, key)
package main

import "fmt"

func main() {
    //Declare
    employeeSalary := make(map[string]int)

    //Adding a key value
    fmt.Println("Adding key")
    employeeSalary["Tom"] = 2000
    fmt.Println(employeeSalary)

    fmt.Println("\nDeleting key")
    delete(employeeSalary, "Tom")
    fmt.Println(employeeSalary)
}

Output

Adding key
map[Tom:2000]

Deleting key
map[]

In above program we delete the key and when we print the map again, the key is not there.

Check if a key exists

Below is the format to check if a key exist in the map

val, ok := mapName[key]

There are two cases

Let’s see an example

package main

import "fmt"

func main() {
    //Declare
    employeeSalary := make(map[string]int)

    //Adding a key value
    employeeSalary["Tom"] = 2000
    fmt.Println("Key exists case")
    val, ok := employeeSalary["Tom"]
    fmt.Printf("Val: %d, ok: %t\n", val, ok)
    fmt.Println("Key doesn't exists case")

    val, ok = employeeSalary["Sam"]
    fmt.Printf("Val: %d, ok: %t\n", val, ok)
}

Output

Key exists case
Val: 2000, ok: true
Key doesn't exists case
Val: 0, ok: false

In the above program when key exists then val variable is set to the actual value which is 2000 here and ok variable is true. When the key doesn’t exist, the val variable is set to 0 which is the default zero value of int and ok variable is false. This ok variable is the best way to check if the key exists in a map or not

In case we only want to check if a key is present and val is not needed, then blank identifier i.e “_” can be used in place of val.

_, ok = employeeSalary["Sam"]

Functions on Maps

Below is the builtin function which can be used on a map

len() function

The len() function can be used to get the length of the map which is number of key value pair present in the map. Below is the format for using this function on map.

len(mapName)

Let’s see a program

package main

import "fmt"

func main() {
    //Declare
    employeeSalary := make(map[string]int)

    //Adding a key value
    employeeSalary["Tom"] = 2000
    employeeSalary["Sam"] = 1200

    lenOfMap := len(employeeSalary)
    fmt.Println(lenOfMap)
}

Output

2

Zero Value

zero value of a map is nil. This is also proved when we declare a map using the var keyword. See below program.

package main

import "fmt"

func main() {
    var employeeSalary map[string]int
    if employeeSalary == nil {
        fmt.Println("employeeSalary map is nil")
    }
}

Output

employeeSalary map is nil

Maps are referenced data types

Map are reference data types. So on assigning one map to a new variable, then both variable refers to the same map. Any change in one of the map would reflect in other and vice versa.

package main

import "fmt"

func main() {
    //Declare
    employeeSalary := make(map[string]int)

    //Adding a key value
    employeeSalary["Tom"] = 2000
    employeeSalary["Sam"] = 1200

    eS := employeeSalary

    //Change employeeSalary
    employeeSalary["John"] = 3000
    fmt.Println("Changing employeeSalary Map")
    fmt.Printf("employeeSalary: %v\n", employeeSalary)
    fmt.Printf("eS: %v\n", eS)

    //Change eS
    employeeSalary["John"] = 4000
    fmt.Println("\nChanging eS Map")
    fmt.Printf("employeeSalary: %v\n", employeeSalary)
    fmt.Printf("eS: %v\n", eS)
}

In the above program, eS is a new map variable to which we assign the existing employeeSalary map.

This shows that maps are referenced data type

Iterate over a map

Range operator can be used to iterate over a map in Go

Let’s define a map first

sample := map[string]string{
        "a": "x",
        "b": "y",
}
for k, v := range sample {
   fmt.Printf("key :%s value: %s\n", k, v)
}

Output:

key :a value: x
key :b value: y
for k := range sample {
   fmt.Printf("key :%s\n", k)
}

Output:

key :a
key :b
for _, v := range sample {
   fmt.Printf("value :%s\n", v)
}

Output:

value :x
value :y
keys := getAllKeys(sample)
fmt.Println(keys)

func getAllKeys(sample map[string]string) []string {
    var keys []string
    for k := range sample {
        keys = append(keys, k)
    }
    return keys
}

Output:

[a b]

Maps are not safe for concurrent use

go maps are not safe for concurrent use.

Buggy code: Below is a buggy code. It might result in crash if concurrent read and write of map happens.

package main

var (
   allData = make(map[string]string)
)

func get(key string) string {
    return allData[key]
}

func set(key string, value string) {
    allData[key] = value
}

func main() {
    go set("a", "Some Data 1")
    go set("b", "Some Data 2")
    go get("a")
    go get("b")
    go get("a")
}

Possible Output:

fatal error: concurrent map read and map write

Correct Code:

We can use a lock to allow concurrent access of the map

package main

import (
    "fmt"
    "sync"
)

var (
    allData = make(map[string]string)
    rwm     sync.RWMutex
)

func get(key string) string {
    rwm.RLock()
    defer rwm.RUnlock()
    return allData[key]

}

func set(key string, value string) {
    rwm.Lock()
    defer rwm.Unlock()
    allData[key] = value

}

func main() {
    set("a", "Some Data")
    result := get("a")
    fmt.Println(result)
}

Output

Some data

Conclusion

This is all about maps in golang. We learned how to create a map, operations on the map, some of the functions defined on a map such as Glen(), how we can iterate over the map, and last but not the least that maps are unsafe for concurrent use. Hope you have liked this article. Please share the feedback/improvements/mistakes in the comments.

Next Tutorial – Method
Previous Tutorial – Slice