What you'll learn
You've used collections since Module 9. Now we go deeper on Kotlin's collection design — especially its clean split between read-only and mutable collections, which is central to writing safe Kotlin.
By the end you'll be able to:
- Distinguish read-only from mutable collections
- Use
List,Set, andMap - Build and modify collections idiomatically
- Choose the right collection for the job
Read-only vs mutable
Kotlin makes a deliberate distinction at the type level. listOf() gives you a read-only List — it has no add or remove methods at all. mutableListOf() gives a MutableList that you can change. Preferring read-only by default makes your code safer and your intent clear:
List — read-only
listOf(...) — no add/remove. Kotlin's default; safer to share.
MutableList — changeable
mutableListOf(...) — full add, remove, and reassignment.
val readOnly = listOf(1, 2, 3) // a read-only List
// readOnly.add(4) // ERROR — no add() method
val mutable = mutableListOf(1, 2, 3) // a MutableList
mutable.add(4) // OK
mutable.removeAt(0)
println(readOnly) // [1, 2, 3]
println(mutable) // [2, 3, 4]Note
List is an interface that simply doesn't expose mutating methods; the underlying object might still change through another reference. But for everyday code, treating listOf as immutable is the right mindset.List, Set & Map
The three core collection types each have a read-only and a mutable version:
- List — ordered, allows duplicates, indexed access (
listOf/mutableListOf) - Set — unique elements, no duplicates (
setOf/mutableSetOf) - Map — key-value pairs (
mapOf/mutableMapOf)
val fruits = listOf("apple", "banana", "apple") // ordered, duplicates OK
val unique = setOf("apple", "banana", "apple") // unique only
val ages = mapOf("Sara" to 25, "Omar" to 30) // key -> value
println(fruits.size) // 3
println(unique) // [apple, banana]
println(ages["Sara"]) // 25Tip
to in "Sara" to 25 — it's an infix function that creates a Pair. It reads beautifully when building maps, and it's pure Kotlin idiom.Working with maps
Maps are everywhere. Access values with map[key] (which returns null if missing), add or update with map[key] = value, and iterate entries by destructuring them into key and value:
val scores = mutableMapOf("Sara" to 90)
scores["Omar"] = 85 // add
scores["Sara"] = 95 // update
for ((name, score) in scores) { // destructure each entry
println("$name: $score")
}
println(scores.getOrDefault("Unknown", 0)) // 0Note
map[key] returns a nullable value, use getOrDefault(key, default) or getOrElse(key) { ... } for a fallback — and everything you learned about null safety in Module 3 applies.Recap & quick check
Key takeaways
- Kotlin splits collections into read-only (listOf) and mutable (mutableListOf) at the type level.
- Prefer read-only collections by default; it's safer and signals intent.
- List = ordered with duplicates; Set = unique; Map = key-value — each with a mutable variant.
- The 'to' infix function builds Pairs, reading naturally in map literals.
- map[key] returns a nullable value; use getOrDefault/getOrElse for a fallback.
Quick check
1. What does listOf() return?
2. Which collection stores only unique elements?
3. What does the 'to' in "Sara" to 25 create?
4. What does map["missing"] return for a key that isn't present?
5. Which should you prefer by default?
Great — collections are second nature now. Next up: Module 21 — Sequences, for processing large data lazily.