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
- Creating read-only lists with listOf
- Mutable lists with mutableListOf (add, remove)
- Sets for unique values and fast membership
- Maps for key -> value lookups
- Iterating with for loops and destructuring
- Safely reading values from a Map
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)
- "Unresolved reference: add" — You called add on a read-only list. Use mutableListOf if you need to change it.
- IndexOutOfBoundsException — Accessing list[5] on a 3-item list crashes. Check size or use getOrNull(i).
- Map lookup returns null — map[key] is nullable. Use ?: for a default, e.g. map["x"] ?: 0.
- Expecting Set to keep order — A plain Set may not preserve insertion order. Use LinkedHashSet (the default from setOf actually does) if order matters.
Pro Tips
- 💡 Default to read-only: use listOf/mapOf unless you truly need to mutate.
- 💡 De-duplicate fast: myList.toSet().toList() removes duplicates while keeping order.
- 💡 Destructure entries: for ((k, v) in map) is cleaner than reading entry.key / entry.value.
📋 Quick Reference — Collections
| Concept | Kotlin Syntax |
|---|---|
| Read-only list | listOf(1, 2, 3) |
| Mutable list | mutableListOf(1) |
| Set | setOf("a", "b") |
| Map | mapOf("k" to 1) |
| Look up safely | map["k"] ?: 0 |
| Loop entries | for ((k, v) in map) |
🎉 Lesson Complete!
- ✅ listOf = read-only, mutableListOf = changeable
- ✅ Set holds unique values with fast membership
- ✅ Map stores key-value pairs built with to
- ✅ Map indexing is nullable — handle the missing key
- ✅ Destructure entries with for ((k, v) in map)
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
- Previous: Default & Named Arguments
- Next: Lambdas & Higher-Order Functions — Pass functions as values and write concise lambda expressions
- Quick reference: Kotlin cheat sheet › Collections
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.