Mutation, references, and copying
val prevents a variable from being reassigned. A mutable collection controls whether the referenced object can change. These are different rules.
val does not make an object immutable
fun main() {
val numbers = mutableListOf(10, 20)
numbers.add(30) // allowed: mutate the object
numbers[0] = 100 // allowed: mutate the object
// numbers = mutableListOf(1, 2) // compile error: reassign val
println(numbers)
}
With var, both mutation and reassignment are possible. Prefer the narrowest mutability that fits the ownership model.
Assignment creates an alias, not a copy
list1 -----+
+----> one MutableList object
list2 -----+
fun main() {
val list1 = mutableListOf("Kotlin", "Java")
val list2 = list1
list2.add("Compose")
println(list1) // [Kotlin, Java, Compose]
println(list1 === list2) // true: same object
println(list1 == list2) // true: equal contents
}
toMutableList() creates another outer list
list1 --------> MutableList object #1
list2 --------> MutableList object #2
fun main() {
val list1 = mutableListOf("Kotlin", "Java")
val list2 = list1.toMutableList()
list2.add("Compose")
println(list1) // [Kotlin, Java]
println(list2) // [Kotlin, Java, Compose]
println(list1 === list2) // false
println(list1 == list2) // false after mutation
}
Copies are normally shallow
Creating a new outer list does not recursively copy mutable elements inside it.
fun main() {
val original = listOf(mutableListOf("Kotlin"))
val copied = original.toList()
copied[0].add("Compose")
println(original) // [[Kotlin, Compose]]
println(original[0] === copied[0]) // true
}
The same trap applies to a data-class copy():
data class User(val name: String, val skills: MutableList<String>)
fun main() {
val user1 = User("Rakesh", mutableListOf("Kotlin"))
val user2 = user1.copy()
user2.skills.add("Compose")
println(user1.skills) // [Kotlin, Compose]
val independent = user1.copy(skills = user1.skills.toMutableList())
independent.skills.add("Android")
println(user1.skills) // [Kotlin, Compose]
println(independent.skills) // [Kotlin, Compose, Android]
}
Mutation operations
MutableCollection provides operations such as add, addAll, remove, removeAll, retainAll, and clear. MutableList additionally supports indexed replacement and removeAt.
Do not expose a ViewModel's internal mutable list and expect callers to behave. Keep mutation inside the owner and publish snapshots or an immutable collection model. Copying protects the outer structure only; nested mutable objects still need an ownership strategy.
Interview questions
- Why can an object referenced by
valstill change? - What is the difference between
==and===for lists? - Does
toList()perform a deep copy? - Why can
data class.copy()still share mutable state? - What should a ViewModel expose to Compose?