skip to content

How Kotlin Does Collections & Sequences

Kotlin layers a read-only/mutable interface split and hundreds of extension operators over the JVM collections, then adds Sequence for lazy pipelines. Understanding that the read-only types are interfaces over the same objects — not new immutable structures — is the key insight.

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

questions

5

In Kotlin, what is the difference between List and MutableList (and Set/MutableSet, Map/MutableMap), and how does the read-only/mutable split work?

level: juniorimportance: must knowfreq 85%

answer

  1. Two interface layers: read-only + Mutable* subtype
  2. MutableList IS-A List (upcast)
  3. Read-only != immutable (backing can change)
  4. listOf vs mutableListOf; buildList { }
  5. Compile-time distinction, same JDK collections at runtime

basics

~10 s

List lets you only read elements; MutableList also lets you add, remove, or change them. The same idea applies to Set/MutableSet and Map/MutableMap. You pick the type based on whether the data should change.

solid answer

~30 s

Kotlin splits each collection into a read-only interface (List, Set, Map) and a Mutable* sub-interface (MutableList, MutableSet, MutableMap). The read-only interfaces expose only query operations (size, get, contains, iteration); the Mutable* ones add mutators (add, remove, put, clear). You create them with listOf/setOf/mapOf (read-only) or mutableListOf/mutableSetOf/mutableMapOf (mutable). The read-only views are not immutable: they are interfaces over a possibly-mutable backing object, so a List reference can be upcast from a MutableList that someone else still mutates. For true immutability you copy or use kotlinx.collections.immutable. This split is compile-time only; at runtime both map to the same JDK collections (e.g. ArrayList).

code

kotlin · 9 lines
kotlin
val nums = mutableListOf(1, 2, 3)
val view: List<Int> = nums      // read-only reference, same object
// view.add(4)                   // won't compile: List has no add
nums.add(4)
println(view)                    // [1, 2, 3, 4]

val frozen = nums.toList()       // defensive copy; independent snapshot
nums.add(5)
println(frozen)                  // [1, 2, 3, 4]

go deeper

for a junior

Knows listOf vs mutableListOf and that List lacks add/remove while MutableList has them.

for a middle

Explains the interface hierarchy (MutableList : List) and that read-only is a view, not immutability.

for a senior

Discusses defensive copies, buildList, kotlinx persistent collections, and Java platform-type interop risks.

for a principal

Reasons about API design: exposing read-only types at boundaries, immutability guarantees, and trade-offs of copying vs persistent data structures for shared state.

## The core idea Kotlin does NOT have separate runtime collection classes for "immutable" and "mutable". Instead it provides two layers of **interfaces**: - **Read-only interfaces**: `Collection<E>`, `List<E>`, `Set<E>`, `Map<K, V>`. These declare only *query* members: `size`, `isEmpty()`, `contains()`, `get()`/`[]`, `iterator()`, plus index/key access. - **Mutable sub-interfaces**: `MutableCollection<E>`, `MutableList<E>`, `MutableSet<E>`, `MutableMap<K, V>`. Each *extends* its read-only parent and adds *mutators*: `add()`, `remove()`, `set()`, `put()`, `clear()`, `removeAt()`, etc. Because `MutableList<E> : List<E>`, every `MutableList` *is a* `List`, so you can pass a mutable list where a read-only one is expected (this is an upcast). ## Read-only is NOT immutable A `List` reference only restricts what *you* can do through that reference. The underlying object may still be a `MutableList` that another holder mutates: ```kotlin val mutable = mutableListOf(1, 2, 3) val readOnly: List<Int> = mutable // upcast, same object mutable.add(4) println(readOnly) // [1, 2, 3, 4] — changed underneath! ``` This is sometimes called the *read-only view* problem. For genuine immutability, copy with `toList()`/`toMutableList()`, or use the `kotlinx.collections.immutable` library (`PersistentList`, `toPersistentList()`). ## Builder functions - Read-only: `listOf()`, `setOf()`, `mapOf()` (and `emptyList()`, `listOfNotNull()`). - Mutable: `mutableListOf()`, `mutableSetOf()`, `mutableMapOf()`, plus the builder DSLs `buildList { }`, `buildSet { }`, `buildMap { }` which give you a mutable receiver and return a read-only result. ## Runtime mapping These are mostly *mapped types* over JDK collections. `listOf()` typically returns a `java.util.Arrays$ArrayList`-style or a Kotlin singleton; `mutableListOf()` returns a `java.util.ArrayList`. The mutable/read-only distinction is enforced by the Kotlin compiler at compile time, not by the JVM at runtime — calling a mutator on a read-only type simply isn't visible in the API. ## Java interop caveat A Java method returning `java.util.List` is seen in Kotlin as a *platform type* `(Mutable)List!`, so the read-only guarantee can be bypassed. Also `listOf()` results may throw `UnsupportedOperationException` if a consumer casts and tries to mutate.

  • If List is read-only, why can a List still change while you hold it?
    Because read-only means the *interface* lacks mutators, not that the object is immutable. The same object may be held elsewhere as a MutableList and mutated. Use toList() to take a snapshot.
  • How do you guarantee a returned collection cannot be mutated by callers?
    Return a defensive copy (toList()) so callers get an independent snapshot, or use kotlinx.collections.immutable persistent collections which truly cannot be modified.

A read-only List is like a window into a room: you can see what's there, but someone with the key (the MutableList reference) can still rearrange the furniture.

saying these in an interview costs you the question

  • Claiming List in Kotlin is immutable / cannot change
  • Thinking there are distinct runtime classes for read-only vs mutable
  • Saying you must cast a List to add elements (you'd need the actual MutableList)
  • Confusing val (reference can't be reassigned) with collection immutability
  • Believing mapOf returns something you can put() into

context

open as a page

Kotlin's collection operators like map, filter, and groupBy aren't methods on the collection classes. Where do they come from, and what does that imply?

level: middleimportance: must knowfreq 80%

basics

~20 s

They are extension functions defined in the Kotlin standard library, not members of the collection classes. They work on any Iterable or Collection, so the same operators apply to lists, sets, and even Java collections.

open as a page

Contrast an eager collection pipeline with a lazy Sequence pipeline in Kotlin. When does asSequence() actually pay off, and when does it not?

level: seniorimportance: must knowfreq 70%

basics

~20 s

A normal list pipeline processes the whole collection at each step, creating a new list every time. A Sequence processes one element through all steps before moving on, with no intermediate lists, and can stop early. Sequences help on big data or early exit.

open as a page

Predict and explain the result types of these on a Set/Map: `setOf(3,1,2).map { it }`, `setOf(1,2,3).filter { it > 1 }`, and `mapOf(1 to "a").filter { it.key == 1 }`. Why do they differ?

level: middleimportance: should knowfreq 45%

basics

~20 s

map on a Set gives a List. filter on a Set gives a List too. filter on a Map gives a Map. The result type depends on which operator and which receiver, because transforming can change duplicates and ordering.

open as a page

You're designing a public Kotlin API. How do you use the read-only/mutable collection model to express intent and protect invariants, given that read-only types aren't truly immutable?

level: seniorimportance: should knowfreq 50%

basics

~10 s

Accept and return read-only types (List, Set, Map) so callers can't change your data, and copy incoming or outgoing collections when you need a real guarantee. Use mutable types only inside your implementation.

open as a page