Collections (List, Set, Map)

Reviewed & published by Brayan K

Kotlin is a modern, concise language with a rich, safe collections library — Lists for ordered data, Sets for unique values, and Maps for key-value pairs, each in read-only and mutable forms.

Part of the free Kotlin course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end of this lesson you'll create and traverse all three collection types and know when to choose each.

What You'll Learn in This Lesson

1️⃣ Lists

A List stores items in order and allows duplicates. listOf(...) makes a read-only list; mutableListOf(...) makes one you can change. You access items by index and iterate with a for loop.

fun main() {
    // listOf creates a READ-ONLY list (you can't add/remove items).
    val fruits = listOf("apple", "banana", "cherry")
    println(fruits)              // -> [apple, banana, cherry]
    println(fruits[0])          // index access -> apple
    println(fruits.size)        // -> 3
    println(fruits.first())     // -> apple
    println("banana" in fruits) // membership -> true

    // mutableListOf creates a list you CAN change.
    val cart = mutableListOf("milk")
    cart.add("eggs")
    cart.add("bread")
    cart.remove("milk")
    println(cart)               // -> [eggs, bread]

    // Iterate with a for loop:
    for (fruit in fruits) {
        println("- $fruit")
    }
}

Notice how "banana" in fruits reads almost like English — the in operator checks membership.

2️⃣ Sets and Maps

A Set keeps only unique values. A Map stores key-value pairs, built with the to keyword. Both come in read-only (setOf, mapOf) and mutable forms.

fun main() {
    // A Set holds UNIQUE values — duplicates are dropped.
    val numbers = setOf(1, 2, 2, 3, 3, 3)
    println(numbers)            // -> [1, 2, 3]
    println(numbers.contains(2))// -> true

    // A Map holds key -> value pairs.
    val ages = mapOf("Ada" to 36, "Linus" to 54)
    println(ages["Ada"])        // look up by key -> 36
    println(ages.keys)          // -> [Ada, Linus]
    println(ages.values)        // -> [36, 54]

    // A mutable map you can update:
    val scores = mutableMapOf("a" to 1)
    scores["b"] = 2             // add or overwrite
    scores["a"] = 10
    println(scores)             // -> {a=10, b=2}

    // Loop over entries:
    for ((name, age) in ages) {
        println("$name is $age")
    }
}

Looping over a map with for ((name, age) in ages) destructures each entry into its key and value — clean and readable.

Your turn. Replace the TODO, then run and compare.

// 🎯 YOUR TURN — replace each TODO then run main()
fun main() {
    // 1) Create a read-only list of three colours
    val colours = TODO("apply listOf", "red", "green", "blue")
    // 👉 replace the TODO(...) above with: listOf("red", "green", "blue")

    // 2) Print how many colours there are
    println("Count: ${colours.size}")

    // 3) Create a map of fruit -> price and look up "apple"
    val prices = mapOf("apple" to 2, "pear" to 3)
    println("Apple costs ${prices["apple"]}")

    // ✅ Expected:
    //    Count: 3
    //    Apple costs 2
}

Mini-Challenge: A Tiny Inventory

Use a mutable map to track stock levels, update a quantity, and check availability.

// 🎯 MINI-CHALLENGE: a tiny inventory.
// Save as Inventory.kt and run main().
//
// 1. Create a mutableMapOf<String, Int>() called stock.
// 2. Add "apples" -> 5 and "pears" -> 0.
// 3. Increase apples by 3 (stock["apples"] = stock["apples"]!! + 3).
// 4. Print the whole map, then print whether pears are in stock
//    (a count greater than 0).
//
// ✅ Example output:
//    {apples=8, pears=0}
//    Pears in stock: false

fun main() {
    // your code here
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Collections

ConceptKotlin Syntax
Read-only listlistOf(1, 2, 3)
Mutable listmutableListOf(1)
SetsetOf("a", "b")
MapmapOf("k" to 1)
Look up safelymap["k"] ?: 0
Loop entriesfor ((k, v) in map)

🎉 Lesson Complete!

Practice quiz

What is the difference between listOf and mutableListOf?

  • listOf is faster
  • mutableListOf cannot be iterated
  • listOf is read-only; mutableListOf can add, remove, and replace
  • They are identical

Answer: listOf is read-only; mutableListOf can add, remove, and replace. listOf gives a read-only list; mutableListOf can be modified.

What happens when you call add on a list created with listOf?

  • It won't compile (unresolved reference: add)
  • It appends silently
  • It returns a new list
  • It throws at runtime

Answer: It won't compile (unresolved reference: add). Read-only lists expose no add; use mutableListOf to modify.

What does a Set guarantee about its values?

  • They stay sorted ascending
  • They allow duplicates
  • They are indexed by position
  • They are unique; duplicates are dropped

Answer: They are unique; duplicates are dropped. A Set holds unique values and discards duplicates.

What does setOf(1, 2, 2, 3, 3, 3) contain?

  • [1, 2, 2, 3, 3, 3]
  • [1, 2, 3]
  • [3]
  • [1]

Answer: [1, 2, 3]. Duplicates are dropped, leaving the unique values 1, 2, 3.

How is a Map built using the to keyword?

  • mapOf("Ada" to 36) creates key-value pairs
  • mapOf("Ada", 36)
  • mapOf("Ada": 36)
  • mapOf("Ada" => 36)

Answer: mapOf("Ada" to 36) creates key-value pairs. The to infix function creates Pair entries for the map.

What does indexing a map with map[key] return when the key is absent?

  • An empty string
  • Throws an exception
  • null (the result type is nullable)
  • Zero

Answer: null (the result type is nullable). Map indexing returns a nullable value; null means the key is missing.

What is an idiomatic way to read a map value with a fallback?

  • ages["Ada"]!!
  • ages["Ada"] ?: 0 using the Elvis operator
  • ages.get("Ada", 0)
  • ages.value("Ada")

Answer: ages["Ada"] ?: 0 using the Elvis operator. The Elvis operator ?: supplies a default when the value is null.

How do you loop over a map's entries with destructuring?

  • for (entry of ages) { }
  • ages.forEach(name, age)
  • for name, age in ages
  • for ((name, age) in ages) { }

Answer: for ((name, age) in ages) { }. Destructuring each entry into (key, value) reads cleanly.

Which collection should you prefer by default in Kotlin?

  • Always mutable
  • Read-only (listOf/mapOf) unless you truly need to mutate
  • Always arrays
  • Always Set

Answer: Read-only (listOf/mapOf) unless you truly need to mutate. Kotlin encourages read-only collections; they are safer and clearer.

When should you use a Set instead of a List?

  • When you need indexed access
  • When duplicates matter
  • When you care about uniqueness and fast membership over order
  • When order must be preserved by position

Answer: When you care about uniqueness and fast membership over order. Sets discard duplicates and offer fast contains checks.

Continue this course

Frequently asked questions

What is the difference between listOf and mutableListOf?

listOf creates a read-only list: you can read items and iterate, but you cannot add, remove, or replace elements. mutableListOf creates a list you can modify with methods like add, remove, and index assignment. Kotlin encourages read-only collections by default because they are safer and easier to reason about; only reach for the mutable variant when you genuinely need to change the contents.

When should I use a Set instead of a List?

Use a Set when you care about uniqueness and membership rather than order or position. A Set automatically discards duplicates and offers very fast contains checks. A List, by contrast, preserves insertion order and allows duplicates, and you access elements by index. If you find yourself repeatedly checking 'does this collection already contain X?', a Set is usually the better fit.

How do I safely read a value from a Map?

Indexing a map with map[key] returns a nullable value — it gives null if the key is absent rather than throwing. So the result type is V? and you should handle the null case, often with the Elvis operator: val age = ages["Ada"] ?: 0. Alternatively, getOrDefault(key, fallback) returns a default in one call, and getValue(key) throws if the key is missing when you are sure it exists.

Is a Kotlin read-only list truly immutable?

Not strictly. listOf returns a List interface that exposes no mutating methods, so through that reference you cannot change it. However, the underlying object may still be a mutable list held elsewhere, and read-only refers to the interface, not a guarantee of deep immutability. For a genuinely unmodifiable collection you can use buildList or the kotlinx.collections.immutable library, but for everyday code the read-only interface is enough.