Start learning free

LESSON 232 OF 338 · HASH TABLE

Hash Maps in Go: map, Comma-ok and delete

Lesson Read the full explanation

Hash Tables, better known as Hash Maps, are just collections of key-value pairs. In other words, they are pieces of data (values) mapped to unique identifiers called properties (keys).

Hash Maps were designed to give us a way of, given a key, associating a value with it for very quick lookups.

In Go, the Hash Map is implemented as a built-in map type.


Creation:

You create an empty Hash Map by declaring a map[keyType]valueType, where the keyType and valueType are the data types of the keys and values.

Let's declare a Hash Map that maps text values represing digits to their numerical value (e.g. "two" => 2). We wil map strings to integers, so the two types are string and int. We will name the map digitValue

digitValue := map[string]int{}

Creating an empty dictionary takes O(1) time.


Initialization:

You can also add some key-value pairs in your dictionary at creation:

digitValue := map[string]int{
	"zero": 0,
	"one":  1,
}

Accessing values:

A value is retrieved from a map by specifying its corresponding key in square brackets ([]):

digitValue := map[string]int{
	"zero": 0,
	"one":  1,
}

// Accesing  values:
fmt.Println(digitValue["zero"]) // prints 0
fmt.Println(digitValue["one"])  // prints 1

fmt.Println(digitValue["three"]) // prints 0

As you can see, if you refer to a key that is not in the map, Go returns a zero value (0 for numbers, empty string for strings). Unlike some other languages, accessing a non-existing key in Go does not add it to the map.

Accessing a value from a map takes O(1) time.


Adding an entry:

Adding an entry to an existing map is simply a matter of assigning a new key and value, via the assignment operator:

digitValue := map[string]int{
	"zero": 0,
	"one":  1,
}

// Adding new entries:
digitValue["three"] = 3

//The new Hash Map:
// {
//	"zero": 0,
//  "one":  1,
//	"three": 3
//}

Adding a new entry to a map takes O(1) time.


Updating an entry:

If you want to update an entry, you can just assign a new value to an existing key:

digitValue := map[string]int{
	"zero": 0,
	"one":  1,
}

// Updating entries:
digitValue["zero"] = 5

//The new Hash Map:
// {
//	"zero": 5,
//  "one":  1
//}

Updating an entry from a map takes O(1) time.


Deleting entries:

The delete() built-in function allows you to remove a key from a map:

digitValue := map[string]int{
	"zero": 0,
	"one":  1,
}

// Removing an entry
delete(digitValue, "zero")

//The new Hash Map:
// {
//  "one":  1
//}

If you try to delete a key that is not in the map, it won't do anything.

Deleting an entry from a map takes O(1) time.


Checking if a key exists:

You can check if a key exists in a map using the comma-ok idiom:

digitValue := map[string]int{
	"zero": 0,
	"one":  1,
}

_, ok := digitValue["zero"]
fmt.Println(ok) // prints true since the key exists

_, ok = digitValue["three"]
fmt.Println(ok) // prints false since the key does not exist in the map

Checking if a key exists in the map is O(1) time.


Iterating over the map keys:

If we want to iterate over keys of the map, we can use the for loop along with the range keyword:

digitValue := map[string]int{
	"zero": 0,
	"one":  1,
}

for key := range digitValue {
    fmt.Println(key, "is", digitValue[key])
}

// This will print:
// one is 1
// zero is 0

Notice the order is not the same as initiated. map keeps the data in random order

Iterating over a dictionary takes O(n) time.


Iterating using both the key and the value for simplicity:

If we want to iterate over both keys and values of the map, we can use the for loop along with the range keyword as follows:

digitValue := map[string]int{
	"zero": 0,
	"one":  1,
}

for key, val := range digitValue {
    fmt.Println(key, "is", val)
}

// This will print:
// one is 1
// zero is 0

Notice the order is not the same as initiated. map keeps the data in random order

Iterating over a map takes O(n) time.


Assignment
Follow the Coding Tutorial and let's practice with maps.


Hint
Look at the examples above if you get stuck.


STEP 1 OF 5
    Ctrl + Enter to check

    GO · HASH TABLE

    Hash Map in Go, explained

    What a hash map is

    A hash map stores key-value pairs and finds a value by its key without scanning the whole collection. Reading, adding, updating, deleting and testing for a key all take O(1) time on average. Go provides the hash map as the built-in map type, written map[KeyType]ValueType. Keys must be comparable with ==, which rules out slices and maps as keys; strings, numbers, booleans and structs of such fields are fine.

    Reach for a map whenever you ask "have I seen this?" or "how many times?" about many items: counting, lookups by name, remembering an index.

    Creating and filling a map

    An empty map is made with a literal or with make:

    digitValue := map[string]int{}
    other := make(map[string]int)
    

    A map can also start with entries. When the closing brace is on its own line, every pair needs a trailing comma, the last one included:

    digitValue := map[string]int{
        "zero": 0,
        "one":  1,
    }
    

    A map that is declared but never created, as in var m map[string]int, is nil. Reading from a nil map works and gives zero values, but writing to it panics with "assignment to entry in nil map". Always create the map with a literal or make before storing anything.

    Reading, adding and updating

    Square brackets read, add and update. Assigning to a new key adds an entry; assigning to an existing key replaces its value:

    digitValue["three"] = 3
    digitValue["zero"] = 5
    digitValue["one"]++
    

    Compound operators such as += and ++ work directly on a map entry, which is why counting is so short in Go.

    Reading a missing key does not fail and does not add the key. It gives the zero value of the value type: 0 for numbers, "" for strings, false for booleans.

    fmt.Println(digitValue["three"], len(digitValue))
    

    With a map that holds only "zero" and "one", this prints:

    0 2
    

    The size is still 2, so the read added nothing.

    The comma-ok idiom

    The zero value creates an ambiguity: digitValue["zero"] is 0 because the key holds 0, and digitValue["three"] is also 0 because the key is missing. A map read can return a second result, a boolean that says whether the key is present:

    v, ok := digitValue["zero"]
    fmt.Println(v, ok)
    _, ok = digitValue["three"]
    fmt.Println(ok)
    
    0 true
    false
    

    Use _ for the value when only the presence matters. The idiom is often placed in the init part of an if, where the variables exist only inside that statement: if v, ok := m[k]; ok { ... }.

    Deleting

    The built-in delete(m, key) removes an entry. Deleting a key that is not there does nothing, so no check is needed first. Deleting from a nil map is also safe.

    Iterating

    for key, val := range m visits every entry, and for key := range m visits the keys only. The order is unspecified and can differ from one run to the next, so never rely on it. When you need a stable order, keep the keys in a slice and loop over the slice, which is what the exercise does. Iteration costs O(n).

    The lesson's exercise

    The exercise tracks a fruit stock. The starting code already declares the slice order := []string{"apples", "bananas", "grapes"}. The steps build on it:

    1. Create fruits as a map[string]int literal with apples 3, bananas 5 and grapes 2.
    2. Add four apples with fruits["apples"] += 4, which makes the count 7.
    3. Test for pears. The comma-ok form in the if init, with the value discarded, prints "Out of pears" when ok is false.
    4. Remove bananas with delete.
    5. Print the stock in a stable order. Loop over order, look each name up with comma-ok, and print only the names still present. Because the bananas are gone, they are skipped.
    fruits := map[string]int{
        "apples": 3,
        "bananas": 5,
        "grapes": 2,
    }
    order := []string{"apples", "bananas", "grapes"}
    
    fruits["apples"] += 4
    
    if _, ok := fruits["pears"]; !ok {
        fmt.Println("Out of pears")
    }
    
    delete(fruits, "bananas")
    
    for _, fruit := range order {
        if count, ok := fruits[fruit]; ok {
            fmt.Println("We have " + strconv.Itoa(count) + " " + fruit + ".")
        }
    }
    

    This prints:

    Out of pears
    We have 7 apples.
    We have 2 grapes.
    

    strconv.Itoa converts the int count to a string. Go does not convert numbers to strings in a + concatenation, and string(count) would produce the character with that code point, not the digits. The program needs the fmt and strconv imports.

    Common mistakes

    • Writing to a nil map, which panics.
    • Using the plain read to test membership when the value type's zero value is a legitimate stored value.
    • Expecting insertion order from range.
    • Forgetting the trailing comma in a multi-line literal, which is a syntax error.

    Key takeaways

    • map[K]V is Go's hash map; create it with a literal or make.
    • Missing keys read as the zero value and are not added.
    • v, ok := m[k] tells a missing key from a stored zero.
    • delete is safe on missing keys; range order is random.
    • All single-key operations are O(1) on average.
    Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free