skip to content

In Kotlin, what is the difference between List and MutableList, and where does that distinction actually exist at runtime?

level: juniorimportance: must knowfreq 70%

answer

  1. Read-only != immutable
  2. Distinction is compile-time only
  3. MutableList extends List + adds add/remove/set
  4. Same object can be aliased as both
  5. No separate immutable runtime class

basics

~20 s

List has no add or remove methods, MutableList does. But it's only a rule the compiler checks. At runtime both are usually the same Java ArrayList, so the read-only promise can be broken from Java code.

solid answer

~30 s

Kotlin splits the collection hierarchy into a read-only view (kotlin.collections.List) and a mutable one (kotlin.collections.MutableList, which extends List and adds add/remove/set). This distinction is purely compile-time: the Kotlin compiler refuses to call mutating methods on a List reference. At runtime there is no separate immutable class — a List is typically just a java.util.ArrayList. So 'read-only' means 'this reference cannot mutate it', not 'this collection can never change'. The same underlying object can be referenced as MutableList elsewhere, or handed to Java, where the read-only contract does not exist at all. Read-only is not the same as immutable.

code

kotlin · 5 lines
kotlin
val mutable = mutableListOf("a", "b")
val view: List<String> = mutable   // read-only reference to SAME object
// view.add("c")  // does not compile
mutable.add("c")                    // allowed via the mutable alias
println(view)                       // [a, b, c]

go deeper

for a junior

Knows List lacks add/remove while MutableList has them, and that listOf is read-only.

for a middle

Articulates that the distinction is compile-time only and that aliasing lets the same object be mutated through another reference.

for a senior

Distinguishes read-only from immutable, knows .toList() defensive copies, and reaches for kotlinx.immutable when true immutability is required.

for a principal

Frames API design tradeoffs: exposing read-only views vs defensive copies vs persistent collections, and the performance/safety implications across module boundaries.

## The two interfaces Kotlin defines its own collection interfaces in the `kotlin.collections` package: - **`List<T>`** — a *read-only* view. It exposes `size`, `get`, `contains`, `iterator`, etc., but **no** `add`, `remove`, `set`, or `clear`. - **`MutableList<T>`** — extends `List<T>` and *adds* the mutating operations (`add`, `remove`, `set`, `clear`). The same pattern exists for `Set`/`MutableSet`, `Map`/`MutableMap`, `Collection`/`MutableCollection`, and `Iterator`/`MutableIterator`. ## "Read-only" is compile-time only This split is enforced **only by the compiler**. There is **no separate immutable runtime class**. When you write `listOf(1, 2, 3)`, you get a `java.util.Arrays$ArrayList`-style object; `mutableListOf(...)` gives you a `java.util.ArrayList`. Both implement `java.util.List`. The word *read-only* describes what the **reference type** lets you do, not a guarantee about the object. - `List` = "through *this* reference you may not mutate." - `Immutable` = "this object can *never* change." Kotlin's stdlib `List` is **not** this. ## Why this matters Because it is compile-time only, the guarantee can be bypassed: ```kotlin val mutable = mutableListOf(1, 2, 3) val readOnly: List<Int> = mutable // upcast — same object mutable.add(4) println(readOnly) // [1, 2, 3, 4] — it changed! ``` The `readOnly` reference cannot call `add`, but the underlying object is shared and was mutated through the other reference. This is *aliasing*, not a compiler bug. ## Key keywords/APIs - `listOf` / `mutableListOf` / `setOf` / `mapOf` - `kotlin.collections.List` vs `kotlin.collections.MutableList` - `.toList()` makes a *defensive copy* (a new read-only snapshot) If you truly need an unchangeable collection, copy defensively (`.toList()`) or use a genuinely immutable type (e.g. the `kotlinx.collections.immutable` library's `PersistentList`).

  • How do you make a genuine read-only snapshot that won't change when the source mutates?
    Call `.toList()` (or `.toSet()`/`.toMap()`) to create a defensive copy — a new collection that does not share storage with the original.
  • Does Kotlin have a truly immutable List in the standard library?
    No. The stdlib only offers read-only *interfaces*. For real immutability use kotlinx.collections.immutable (PersistentList/ImmutableList) or copy defensively.

A List reference is like a museum window: you can look but not touch — yet someone with the door key (a MutableList alias) can still rearrange the exhibit.

saying these in an interview costs you the question

  • Claiming List is immutable and can never change
  • Saying listOf returns a special immutable class
  • Thinking upcasting to List copies the data
  • Believing the compiler prevents all mutation of the underlying object

context