Phase 4 · Collections & GenericsModule 20~34 min read

Collections in Depth

Read-only vs mutable Lists, Sets, and Maps, and how to choose between them.

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, and Map
  • 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:

Two kinds of collection

List — read-only

listOf(...) — no add/remove. Kotlin's default; safer to share.

MutableList — changeable

mutableListOf(...) — full add, remove, and reassignment.

Mutability.kt
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

"Read-only" isn't quite the same as "immutable" — a read-only 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)
Types.kt
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"])     // 25

Tip

Notice the 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:

Maps.kt
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))   // 0

Note

Because 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.