Explain why `val` does not guarantee immutability. How do you actually express an immutable value in Kotlin?
answer
- val = binding immutable; object can still mutate
- List is read-only, NOT immutable (backing may be ArrayList)
- data class + copy() for value semantics
- Don't leak the MutableList backing field
- kotlinx.collections.immutable for true immutability
basics
~20 sval only stops you from pointing the name at a different object. The object it points to can still change if it is mutable. For real immutability you also need an immutable type, like List instead of MutableList.
solid answer
~40 s`val` controls the *binding*, not the *object's internal state*. A `val` of type `MutableList<Int>` cannot be reassigned, but you can still call `add`/`remove` on it. True immutability requires the *type* to be immutable as well: use read-only interfaces like `List`, `Set`, `Map` (created via `listOf`, `mapOf`), or `data class` instances whose properties are all `val` and themselves immutable. Even read-only `List` is not a deep guarantee — the elements could be mutable, and the underlying object might be an `ArrayList` exposed elsewhere. For value semantics use `data class` + `copy()` to produce new instances instead of mutating. Kotlin also has experimental `@JvmInline value class` and immutable collections in `kotlinx.collections.immutable` (`persistentListOf`) for stronger guarantees.
code
kotlin · 5 linesclass Cart {
private val _items = mutableListOf<String>() // mutable internally
val items: List<String> get() = _items // read-only outside
fun add(item: String) { _items.add(item) }
}go deeper
Recognizes that val of a MutableList still allows add/remove.
Distinguishes read-only List from immutable, and uses data class + copy() for value semantics.
Avoids leaking backing collections and reaches for persistent/immutable collections when guarantees matter; reasons about aliasing.
Designs APIs around immutability-by-default, weighing shallow vs deep immutability and concurrency implications.
## Binding immutability vs. object immutability There are two independent axes: 1. **Binding (reference) mutability** — can the *name* be pointed at a different object? Controlled by `val` (no) vs `var` (yes). 2. **Object (state) mutability** — can the *object's internal data* change? Controlled by the object's *type/design*, not by the keyword. `val` only addresses axis 1. ```kotlin val sb = StringBuilder("a") sb.append("b") // object mutated — still "ab" is fine // sb = StringBuilder() // ERROR — binding is frozen ``` ## Read-only vs. mutable collection interfaces Kotlin separates read-only and mutable collection *interfaces*: - `List`, `Set`, `Map` — **read-only** views (no `add`/`put`). - `MutableList`, `MutableSet`, `MutableMap` — expose mutators. ```kotlin val ro: List<Int> = listOf(1, 2) // ro.add(3) // no such method — read-only interface val mut: MutableList<Int> = mutableListOf(1, 2) mut.add(3) // allowed ``` Important nuance: `List` is **read-only, not immutable**. The actual runtime object is often an `ArrayList`. If some other reference holds it as `MutableList`, it can change underneath you: ```kotlin val backing = mutableListOf(1) val view: List<Int> = backing // read-only view backing.add(2) // view now sees [1, 2]! ``` ## Expressing genuine immutability - **`data class` with all-`val` properties** for value objects; evolve with `copy()` instead of mutation: ```kotlin data class Point(val x: Int, val y: Int) val p = Point(1, 2) val moved = p.copy(x = 5) // new instance; p unchanged ``` - **Don't leak the mutable backing collection** — expose `List`, build internally with `MutableList`. - **`kotlinx.collections.immutable`** — `persistentListOf`, `ImmutableList`/`PersistentList` give true structural immutability and sharing. - **`@JvmInline value class`** — wraps a single immutable value with no identity. ## Deep vs. shallow Even an all-`val` `data class` is only *shallowly* immutable if a property's type is itself mutable (e.g., a `val tags: MutableList<String>`). Deep immutability requires immutable types all the way down.
- Is a read-only List truly immutable?No. It hides mutators, but the underlying object may be a MutableList held elsewhere and can change. For guaranteed immutability use persistent collections.
- How do you safely expose internal mutable state?Keep a private MutableList backing field and expose it typed as List (often via a custom getter), so callers can't mutate it.
val is a locked car key holder — the key can't be swapped, but anyone can still drive the car around (mutate it).
saying these in an interview costs you the question
- Equating val with deep immutability
- Thinking listOf returns something unmodifiable that can never change via aliasing
- Mutating an object returned from copy() and expecting the original to change
- Exposing a public MutableList property instead of List
- Confusing read-only (interface lacks mutators) with immutable (object truly can't change)