Kotlin Maps and Sets

Store key-value pairs in maps and unique values in sets, read and update them safely, loop over entries, and count and group with maps.

Lesson 10 of 43Intermediate18 min read

Lists are great when items have an order, but often you want to look something up by a name or code instead: a word's definition, a product's price by its code, a user's score by their username. A map stores key-value pairs and finds a value by its key almost instantly, however many entries there are.

A set answers a different question: "have I seen this before?" It holds unique values, quietly ignoring duplicates, which makes it perfect for tags, visited pages or the distinct cities in a list of customers.

Like lists, both come in read-only (mapOf, setOf) and mutable (mutableMapOf, mutableSetOf) versions. In this lesson you'll create, read, update and loop over maps, use them to count things, and combine sets with union and intersection.

Creating and reading maps

Kotlin
val prices = mapOf("tea" to 2.5, "cake" to 3.75, "soup" to 4.0)
println(prices["tea"])                   // 2.5
println(prices["pizza"])                 // null
println(prices.getOrDefault("pizza", 0.0))  // 0.0
  • "tea" to 2.5 creates a pair: the key and its value.
  • map[key] returns the value, or null if the key isn't there, so its type is nullable (Double?).
  • getOrDefault(key, fallback), or map[key] ?: fallback, gives a safe default.
  • Keys are unique; values can repeat.
  • "tea" in prices checks whether a key exists. keys, values and size describe the map.

Changing a mutable map

Kotlin
val stock = mutableMapOf("pen" to 300, "notebook" to 120)
stock["ruler"] = 50          // add a new entry
stock["pen"] = 280           // update an existing one
stock.remove("notebook")     // remove by key
stock["pen"] = (stock["pen"] ?: 0) - 1

Setting a value with map[key] = value adds the key if it's new and replaces the value if it exists. getOrPut(key) { default } returns the existing value, or adds the default and returns it, which is handy for building up maps of lists.

Looping and counting

Loop over entries and unpack each one into a key and a value:

Kotlin
for ((item, price) in prices) {
    println("$item costs $price")
}

Maps are the classic tool for counting. Start each new key at zero and add one:

Kotlin
val counts = mutableMapOf<String, Int>()
for (word in words) {
    counts[word] = (counts[word] ?: 0) + 1
}

Kotlin's groupingBy { it }.eachCount() does the same in one line, as the next lesson shows. mapOf keeps entries in the order they were inserted, so output is predictable.

Sets

Kotlin
val tags = mutableSetOf("kotlin", "android")
tags.add("kotlin")         // ignored: already there
println(tags)              // [kotlin, android]
OperationResult
a union bEverything in either set
a intersect bItems in both
a subtract bItems in a but not b
list.toSet()Removes duplicates from a list

Checking x in set is very fast, even for huge sets, which makes sets ideal for "have we seen this?" checks.

Filtering and transforming maps

Read-only maps have functions that return new maps, just like lists do. Inside the lambda, it is one entry with a key and a value:

Kotlin
val prices = mapOf("tea" to 2.5, "cake" to 3.75, "soup" to 4.0)

prices.filterValues { it > 3.0 }          // {cake=3.75, soup=4.0}
prices.mapValues { it.value * 2 }         // {tea=5.0, cake=7.5, soup=8.0}
prices.mapKeys { it.key.uppercase() }     // {TEA=2.5, CAKE=3.75, SOUP=4.0}
prices.toSortedMap()                      // {cake=3.75, soup=4.0, tea=2.5}
prices.maxBy { it.value }.key             // soup
prices + ("jam" to 1.0)                   // a new map with jam added
prices - "tea"                            // a new map without tea

filterKeys works like filterValues, and plain filter gives you the whole entry when you need both parts. toSortedMap() orders the entries by key, which is handy for printing a report alphabetically.

You can also build a map from a list, without a loop:

Kotlin
val words = listOf("kiwi", "fig", "banana")
words.associateWith { it.length }   // {kiwi=4, fig=3, banana=6}
words.associateBy { it.first() }    // {k=kiwi, f=fig, b=banana}

associateWith uses each item as a key and computes a value; associateBy computes a key and keeps the item as the value. If two items produce the same key, the later one wins, so associateBy silently drops data when keys clash.

When a missing key should be an error

Most of the time map[key] returning null is exactly what you want. Sometimes, though, a missing key means a bug, and you'd rather find out immediately. getValue(key) returns a non-null value or throws a NoSuchElementException with the message "Key pizza is missing in the map.".

Sets have a similar convenience: add returns true if the item was new and false if it was already there. That lets you check and record in one step:

Kotlin
val seen = mutableSetOf<String>()
if (!seen.add(email)) println("Duplicate: $email")

Kotlin Maps Syntax

Maps and sets

val m = mapOf("a" to 1, "b" to 2)
val mm = mutableMapOf<String, Int>()
m["a"]   m.getOrDefault("z", 0)   "a" in m   m.keys   m.values
mm["x"] = 5   mm.remove("x")   mm.getOrPut("y") { 0 }
for ((k, v) in m) { }
val s = setOf(1, 2, 3)
s union setOf(4)   s intersect setOf(2, 9)   s subtract setOf(1)
listOf(1, 1, 2).toSet()

Kotlin Maps Summary

  • mapOf("key" to value) stores key-value pairs; keys are unique.
  • map[key] returns a nullable value; use getOrDefault or ?: for a fallback.
  • map[key] = value adds or updates entries in a mutable map; remove deletes.
  • Loop with for ((key, value) in map); maps are ideal for counting.
  • Sets hold unique values and support union, intersect and subtract.

Kotlin Maps Examples

Each example shows the code, the output it produces and what each part does.

Example 1

A price list

Kotlin
fun main() {
    val menu = mapOf("tea" to 2.5, "coffee" to 2.8, "cake" to 3.75)
    val order = listOf("tea", "cake", "pizza", "tea")

    var total = 0.0
    for (item in order) {
        val price = menu[item]
        if (price == null) {
            println("Sorry, no $item")
        } else {
            total += price
        }
    }
    println("Total: £$total")
    println("Menu has ${menu.size} items: ${menu.keys}")
}
Output
Sorry, no pizza
Total: £8.75
Menu has 3 items: [tea, coffee, cake]
Line-by-line explanation
  1. Line 7Looking up a missing key gives null rather than crashing.
  2. Line 11After the null check, price is smart cast to Double.
  3. Line 15keys is the set of all keys, in insertion order.
Example 2

Counting words

Kotlin
fun main() {
    val text = "the cat sat on the mat the end"
    val counts = mutableMapOf<String, Int>()
    for (word in text.split(" ")) {
        counts[word] = (counts[word] ?: 0) + 1
    }
    println(counts)

    for ((word, n) in counts) {
        if (n > 1) println("'$word' appears $n times")
    }
}
Output
{the=3, cat=1, sat=1, on=1, mat=1, end=1}
'the' appears 3 times
Line-by-line explanation
  1. Line 5A missing word counts as 0, then 1 is added.
  2. Line 7Maps print as {key=value, ...}.
  3. Line 9Destructuring gives each entry's key and value names.
Example 3

Sets of tags

Kotlin
fun main() {
    val alice = setOf("kotlin", "android", "design")
    val bob = setOf("kotlin", "ios", "design", "swift")

    println("Both like: ${alice intersect bob}")
    println("Anyone likes: ${alice union bob}")
    println("Only Alice: ${alice subtract bob}")

    val visits = listOf("home", "shop", "home", "cart", "shop")
    println("Unique pages: ${visits.toSet()}")
}
Output
Both like: [kotlin, design]
Anyone likes: [kotlin, android, design, ios, swift]
Only Alice: [android]
Unique pages: [home, shop, cart]
Line-by-line explanation
  1. Line 5intersect keeps items found in both sets.
  2. Line 6union combines both, without duplicates.
  3. Line 7subtract keeps Alice's tags that Bob doesn't have.
  4. Line 10toSet removes duplicates while keeping first-seen order.

Common Kotlin Maps Mistakes

Assuming a key exists

Don't
val price: Double = menu["pizza"]       // Type mismatch: Double? is not Double
Do
val price: Double = menu["pizza"] ?: 0.0

Updating a read-only map

Don't
val scores = mapOf("Ana" to 10)
scores["Ana"] = 12                       // error: no set method
Do
val scores = mutableMapOf("Ana" to 10)
scores["Ana"] = 12

Using a list for membership checks on big data

Don't
val seen = mutableListOf<String>()
if (email !in seen) seen.add(email)      // slower and slower as the list grows
Do
val seen = mutableSetOf<String>()
seen.add(email)                          // duplicates are ignored automatically

Kotlin Maps in Real Projects

Maps and sets in apps

Apps cache downloaded images in maps keyed by URL, store settings as key-value pairs, and translate interface text with maps from keys to phrases. Sets track which items a user has selected or which notifications they've already seen. Under the hood, mutableMapOf creates a LinkedHashMap and mutableSetOf a LinkedHashSet, which keep insertion order.

Kotlin Maps FAQ

How do I get a value from a map with a default in Kotlin?

Use the Elvis operator, map[key] ?: 0, or map.getOrDefault(key, 0). Both leave the map unchanged. If you also want to store the default for next time, use getOrPut(key) { 0 } on a mutable map.

Does a Kotlin map keep insertion order?

Maps from mapOf and mutableMapOf do, because they're backed by a LinkedHashMap. A hashMapOf map makes no ordering promise. If you want entries sorted by key instead, call toSortedMap() or create one with sortedMapOf().

How do I sort a map by value in Kotlin?

Maps themselves aren't sorted by value, but you can sort their entries: prices.entries.sortedBy { it.value } gives a list of entries from cheapest to dearest. Add .associate { it.toPair() } to turn the result back into a map, which keeps that order.

How do I remove duplicates from a list in Kotlin?

Call distinct() to get a new list with duplicates removed and the first occurrence of each item kept in place. toSet() does the same but gives you a Set. To remove duplicates by a property, such as email address, use distinctBy { it.email }.

What is the difference between a Map and a Set?

A Set holds unique values on their own, such as a collection of tags. A Map holds unique keys, each linked to a value, such as product names linked to prices. In fact, a map's keys property is a Set, because keys can't repeat.

Kotlin Maps Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the line so it sets the price of juice to 1.9.

prices[""] = 1.9
Predict the outputeasy

What does this print?

val m = mapOf("a" to 1, "b" to 2)
println(m["c"])
Predict the outputmedium

What does this print?

val s = mutableSetOf(1, 2, 3)
s.add(2)
s.add(4)
println(s.size)
Multiple choicemedium

Which expression gives every item in set a that isn't in set b?

Predict the outputhard

What does this print?

val counts = mutableMapOf<Char, Int>()
for (c in "banana") counts[c] = (counts[c] ?: 0) + 1
println(counts)

Kotlin Maps Quiz

5 questions to check what you learned.

  1. What does "tea" to 2.5 create?

    • A list
    • A string
    • A set
    • A Pair, used as a map entry
  2. What happens when you add a duplicate to a set?

    • Nothing: the set keeps one copy
    • An exception
    • It's added twice
    • The old value is removed
  3. Which gives a value for a key, or 0 if it's missing?

    • map[key]!!
    • map.getOrDefault(key, 0)
    • map.get(key, 0)
    • map.find(key)
  4. How do you loop over a map's keys and values?

    • for (entry in map.keys)
    • map.loop { }
    • for ((key, value) in map)
    • for (i in 0 until map.size)
  5. Why is x in set faster than x in list for large collections?

    • Sets are smaller
    • Sets use hashing to jump to the item, while lists check items one by one
    • Lists are sorted
    • It isn't faster