skip to content

Java Collections As (Mutable) Views

A java.util.List arrives in Kotlin as a MutableList, and Kotlin's read-only interfaces exist only at compile time. That means a Java caller can mutate a collection you declared as List — the answer to why read-only is not immutable.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

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

open as a page

When a Java method returns java.util.List<String>, what Kotlin type does it appear as, and what are the safety implications of that mapping?

level: middleimportance: must knowfreq 65%

basics

~20 s

It usually shows up as a MutableList, so Kotlin lets you add and remove from it. But Java may not expect that list to change, so calling add could throw or corrupt the caller's data.

open as a page

What does `.toList()` do versus simply upcasting a MutableList to List, and when must you prefer toList()?

level: middleimportance: should knowfreq 50%

basics

~20 s

Upcasting just changes the reference type but keeps the same object, so changes still leak through. toList() makes a brand-new copy, so later changes to the original don't affect it. Use toList() when you need a stable, independent snapshot.

open as a page

You declare `fun process(items: List<String>)` in Kotlin and call it from Java. Can the Java caller add elements to that list? Explain.

level: seniorimportance: should knowfreq 45%

basics

~10 s

Yes. Kotlin's read-only List is just a java.util.List to Java, so Java sees the normal add and remove methods and can change the list, even though Kotlin marked the parameter as read-only.

open as a page

How would you expose a collection across a Java/Kotlin module boundary so that NO caller — Kotlin or Java — can mutate it?

level: principalimportance: nice to knowfreq 30%

basics

~10 s

A plain Kotlin read-only List isn't enough because Java can still change it. Use a defensive copy wrapped so mutation throws, or a truly immutable collection type whose add/remove always fail for everyone.

open as a page