Start learning free

LESSON 236 OF 348 · HASH TABLE

Hash Maps in Kotlin: MutableMap, Lookups and Counting

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 Java, the Hash Map is implemented as a HashMap


Creation:

You create an empty Hash Map by declaring an Map<keyType, valueType> and initializing it with using new HashMap<>(), 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 Integer. We will name the map digitValue

val digitValue: MutableMap<String, Int> = mutableMapOf()

Creating an empty dictionary takes O(1) time.


Initialization:

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

val digitValue: MutableMap<String, Int> = mutableMapOf(
    "zero" to 0,
    "one" to 1
)

Accessing values:

A value is retrieved from a map using the get() method:

val digitValue: MutableMap<String, Int> = mutableMapOf(
    "zero" to 0,
    "one" to 1
)

// Accesing values:
digitValue["zero"] // returns  0

digitValue["one"] //  returns 1

digitValue["three"] // returns null since 'three' does not exist

digitValue.getOrDefault("three", 0) // returns 0 as the default value

As you can see, if you refer to a key that is not in the map, Java raises an exception. We can use getOrDefault to specify a default value in case it does not exist

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


Adding an entry:

We can add a key / value pair by using the put() method as follows:

val digitValue: MutableMap<String, Int> = mutableMapOf(
    "zero" to 0,
    "one" to 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:

We can update an existing entry by using the put() method as follows:

val digitValue: MutableMap<String, Int> = mutableMapOf(
    "zero" to 0,
    "one" to 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 remove() method allows you to remove a key from a map:

val digitValue: MutableMap<String, Int> = mutableMapOf(
    "zero" to 0,
    "one" to 1
)

// removing entries:
digitValue.remove("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 containsKey() method:

val digitValue: MutableMap<String, Int> = mutableMapOf(
    "zero" to 0,
    "one" to 1
)


digitValue.containsKey("zero") // returns true since the key exists

digitValue.containsKey("three") // returns false since the key does not exist

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 : operator and the keySet() method as follows:

val digitValue: MutableMap<String, Int> = mutableMapOf(
    "zero" to 0,
    "one" to 1
)

for (key in digitValue.keys) {
    println("$key is ${digitValue[key]}")
}

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

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

Iterating over a dictionary takes O(n) time.



Assignment
Follow the Coding Tutorial and let's play with some dictionaries.


Hint
Look at the examples above if you get stuck.


STEP 1 OF 5
    Ctrl + Enter to check

    KOTLIN · HASH TABLE

    Hash Map in Kotlin, explained

    What a hash map is

    A hash map stores key-value pairs. You give it a key, and it gives back the value stored under that key, usually in constant time, no matter how many entries it holds. Each key appears at most once. Use a map whenever you need to look something up by name rather than by position: counting how many times each word appears, remembering which index a number was seen at, or translating text such as "two" into the number 2.

    In Kotlin you normally work through the MutableMap<K, V> interface, a map you can change. On the JVM, the standard builders return hash-based maps, so lookups, insertions and removals take O(1) time on average. Writing the key and value types out, as in MutableMap<String, Int>, is optional when the compiler can infer them.

    Creating a map

    An empty map needs its types spelled out, because there is nothing to infer them from:

    val digitValue: MutableMap<String, Int> = mutableMapOf()
    

    You can also fill it at creation. Each pair is written key to value, and the types are inferred:

    val digitValue = mutableMapOf(
        "zero" to 0,
        "one" to 1
    )
    

    Two other forms are common. HashMap<String, Int>() creates a hash map directly. mapOf(...) creates a read-only Map: you can look things up, but map["a"] = 2 doesn't compile. Prefer the read-only type when nothing should change.

    Reading, adding, updating and removing

    Square brackets read a value. Reading a key that isn't in the map does not throw; it gives null:

    val digitValue = mutableMapOf("zero" to 0, "one" to 1)
    
    println(digitValue["zero"])   // 0
    println(digitValue["three"])  // null
    println(digitValue.getOrDefault("three", 0))  // 0
    println(digitValue["three"] ?: 0)             // 0
    

    Because a missing key gives null, the type of digitValue["zero"] is Int?, a nullable Int, not Int. That has consequences. val x: Int = digitValue["zero"] is a compile error (the compiler reports that the type is Int? but Int was expected), and so is digitValue["zero"] + 1. There are three ways out:

    • getOrDefault(key, fallback) returns the fallback for a missing key, and its type is a plain Int.
    • The elvis operator ?: does the same for any nullable value: map[key] ?: 0.
    • map[key]!! asserts the value isn't null. If the key is missing, it throws a NullPointerException. Use it only after you've checked that the key exists.

    Assigning through square brackets adds a new entry, or replaces the value of an existing key:

    val digitValue = mutableMapOf("zero" to 0, "one" to 1)
    
    digitValue["three"] = 3   // adds
    digitValue["zero"] = 5    // replaces
    println(digitValue)       // {zero=5, one=1, three=3}
    

    The assignment form is shorthand for put(key, value). The put call also returns the value that was there before, or null if the key was new.

    remove(key) deletes an entry. It returns the removed value, and for a missing key it does nothing and returns null:

    val digitValue = mutableMapOf("zero" to 0, "one" to 1)
    
    digitValue.remove("zero")
    println(digitValue)                 // {one=1}
    println(digitValue.remove("nine"))  // null
    

    To test for a key, use containsKey(key) or the equivalent key in map:

    println(digitValue.containsKey("one"))  // true
    println("nine" in digitValue)           // false
    

    Iterating

    A for loop over map.keys visits each key, and you look up the value with brackets. A destructuring loop gives both at once:

    val digitValue = mutableMapOf("zero" to 0, "one" to 1)
    
    for (key in digitValue.keys) {
        println("$key is ${digitValue[key]}")
    }
    
    for ((key, value) in digitValue) {
        println("$key -> $value")
    }
    

    Both loops take O(n) time for a map of n entries. Inside a string, $key inserts a variable and ${...} inserts any expression, which is why the lookup needs braces.

    Order

    A hash map doesn't promise any particular order of keys, and code shouldn't depend on one. The maps built by mutableMapOf on the JVM happen to remember insertion order, so the loops above print zero before one. A map made with HashMap() makes no such promise. If the order matters to your logic, sort the keys yourself.

    Common mistakes

    • Treating map[key] as an Int. It is an Int?. Use getOrDefault, ?: or a check first.
    • Using !! without checking. It turns a missing key into a NullPointerException.
    • Using a read-only map for updates. A value of type Map has no set operator, so map[key] = value doesn't compile. Declare it as MutableMap or build it with mutableMapOf.
    • Expecting duplicate keys. Assigning to an existing key replaces its value. A second entry is never created.
    • Expecting a particular order. Don't rely on it.

    The lesson's exercise

    The exercise builds a small fruit inventory and uses each operation once. The steps:

    1. Create fruits as a MutableMap<String, Int> using mutableMapOf, with three pairs: apples 3, bananas 5, grapes 2.
    2. Add 4 apples. Read the current count with getOrDefault("apples", 0), add 4, and assign the result back with brackets. The default of 0 makes the line safe even if the key were missing.
    3. Print "Out of pears" when fruits.containsKey("pears") is false. The ! negates the check.
    4. Remove the bananas with remove("bananas").
    5. Report every fruit. Loop over fruits.keys with the variable fruit and print a string template that includes ${fruits[fruit]} and $fruit.
    val fruits: MutableMap<String, Int> = mutableMapOf(
        "apples" to 3,
        "bananas" to 5,
        "grapes" to 2
    )
    
    fruits["apples"] = fruits.getOrDefault("apples", 0) + 4
    
    if (!fruits.containsKey("pears")) {
        println("Out of pears")
    }
    
    fruits.remove("bananas")
    
    for (fruit in fruits.keys) {
        println("We have ${fruits[fruit]} $fruit.")
    }
    

    It prints:

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

    The line order of the last two lines follows the map's iteration order, which in this program is insertion order.

    Counting with a map

    The most common use of a map in problem solving is counting. Read the current count with a default of 0, add one, and store it back:

    val counts = mutableMapOf<Char, Int>()
    for (c in "banana") {
        counts[c] = counts.getOrDefault(c, 0) + 1
    }
    println(counts)  // {b=1, a=3, n=2}
    

    The same pattern solves problems that ask for the frequency of letters, the first repeated value, or the index of a previously seen number.

    Key takeaways

    • A map holds key-value pairs, with unique keys. Lookup, insertion and removal are O(1) on average.
    • Create one with mutableMapOf(...), or mutableMapOf() with explicit types when it starts empty.
    • map[key] returns null for a missing key, so its type is nullable. Use getOrDefault or ?:.
    • map[key] = value adds or replaces; remove(key) deletes; containsKey(key) or in checks.
    • Don't rely on key order, and use a read-only Map when nothing should change.
    Keep going with KotlinAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free