listOf(), setOf(), and mapOf() create read-only collections — List, Set, and Map interfaces exposing no add/remove/put methods at all. mutableListOf(), mutableSetOf(), and mutableMapOf() create their mutable counterparts, which DO expose those methods. The genuinely important nuance: List is a read-only VIEW of the interface, not a deep immutability guarantee — if the same underlying object is also referenced somewhere as a MutableList, changes made through that mutable reference are visible through the read-only List reference too, since they're the same object in memory.
Read-only vs mutable collections
numbers has no add() or remove() method at all — attempting to call one is a compile error, since List simply doesn't expose those methods in its interface.
val numbers: List<Int> = listOf(1, 2, 3) // read-only interface — no add/remove available
// numbers.add(4) // COMPILE ERROR: List has no add() method
val mutableNumbers: MutableList<Int> = mutableListOf(1, 2, 3)
mutableNumbers.add(4) // fine — MutableList exposes add()
println(mutableNumbers) // [1, 2, 3, 4]The real gotcha: List is a view, not a deep guarantee
readOnlyView and actualList refer to the EXACT SAME underlying object — the List reference just doesn't expose mutating methods on itself, but it doesn't stop the object from actually changing through a different, mutable reference to it.
val actualList = mutableListOf(1, 2, 3)
val readOnlyView: List<Int> = actualList // same object, viewed through a read-only interface
actualList.add(4) // mutating through the MUTABLE reference
println(readOnlyView) // [1, 2, 3, 4] — the "read-only" view SAW the change too!Common operations: functional-style, not loop-based
map, filter, and sum (among many others) return a NEW collection or value rather than mutating the original — idiomatic Kotlin favors this functional style over manually indexed loops for most collection processing.
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 } // [2, 4, 6, 8, 10]
val evens = numbers.filter { it % 2 == 0 } // [2, 4]
val total = numbers.sum() // 15
val names = mapOf("a" to 1, "b" to 2) // Map, built from pairs directly