Explain `buildList`, `buildSet`, and `buildMap`. What is the type of the receiver inside the block, and what type is returned?
answer
- Receiver is Mutable*, return is read-only interface
- buildList -> List, buildSet -> Set, buildMap -> Map
- Replaces mutableListOf().apply{} but tighter type
- Don't retain/mutate builder after block
- Insertion order preserved; capacity overload exists
basics
~10 sEach gives you a temporarily editable collection inside the lambda (you call add, put, etc.), and when the block ends it hands back a normal read-only List, Set, or Map.
solid answer
~40 s`buildList`, `buildSet`, and `buildMap` are stdlib inline functions that expose a *temporarily mutable* builder via a receiver lambda and return a *read-only* collection. Inside `buildList { }` the receiver is a `MutableList<E>`; inside `buildSet { }` it's a `MutableSet<E>`; inside `buildMap { }` it's a `MutableMap<K, V>`. You mutate it (`add`, `addAll`, `put`, `[k] = v`, `remove`), and the function returns the corresponding read-only interface (`List`, `Set`, `Map`). The returned collection should be treated as immutable; you must not retain a reference to the builder and mutate it after the block. Each has a `capacity` overload for sizing. They replace the older idiom `mutableListOf<...>().apply { ... }` while returning a tighter, read-only type instead of leaking the mutable one.
code
kotlin · 7 linesfun pages(total: Int, current: Int): List<Int> = buildList {
add(1)
if (current > 3) add(-1) // ellipsis marker
for (p in maxOf(2, current - 1)..minOf(total - 1, current + 1)) add(p)
if (current < total - 2) add(-1)
if (total > 1) add(total)
}go deeper
Knows these build collections via a mutable lambda and return read-only results.
States receiver vs return types precisely and the read-only contract; uses them for conditional/imperative construction.
Explains the capacity overload, insertion-order backing, and why returning the read-only interface is safer than leaking the mutable builder.
Discusses the don't-retain-builder contract, stabilization history (experimental -> 1.6), and when builders beat functional pipelines for readability/allocation.
## The three functions ```kotlin public inline fun <E> buildList(builderAction: MutableList<E>.() -> Unit): List<E> public inline fun <E> buildSet(builderAction: MutableSet<E>.() -> Unit): Set<E> public inline fun <K, V> buildMap(builderAction: MutableMap<K, V>.() -> Unit): Map<K, V> ``` Each also has a `capacity: Int` overload, e.g. `buildList(capacity) { ... }`. Key idea: the lambda's **receiver** (`this`) is the *mutable* builder; the function's **return type** is the *read-only* interface. | Function | Receiver (`this`) | Returns | |---|---|---| | `buildList` | `MutableList<E>` | `List<E>` | | `buildSet` | `MutableSet<E>` | `Set<E>` | | `buildMap` | `MutableMap<K, V>` | `Map<K, V>` | ## Usage ```kotlin val xs: List<Int> = buildList { add(1) addAll(listOf(2, 3)) if (cond) add(4) } val m: Map<String, Int> = buildMap { put("a", 1) this["b"] = 2 // operator set putIfAbsent("a", 99) // no-op, key exists } val s: Set<String> = buildSet { add("x"); add("x") // duplicates collapse } ``` ## Why use them instead of `mutableListOf { }.apply` - **Intent + type tightening**: you get back a `List`/`Set`/`Map` (read-only interface), not a `MutableList`. Callers can't accidentally mutate it. - **Conditional/loopy construction**: cleaner than chaining `+`/`plus` or `filter`/`map` when the logic is imperative. - **One allocation**: it builds in place rather than producing intermediate collections. ## Immutability caveat The returned collection is *read-only by interface*, but it is the same underlying builder "frozen". The contract says you must **not** hold onto the builder reference and mutate it after the block returns — doing so is undefined and can corrupt the supposedly read-only result. In practice these were stabilized in Kotlin 1.6 (previously experimental). ## `buildList` indexed builder Note `buildList` does **not** give you an index parameter; you write ordinary loops. For deterministic ordering, `buildList`/`buildMap`/`buildSet` preserve insertion order (backed by `ArrayList` / `LinkedHashMap` / `LinkedHashSet`).
- If `buildMap` returns a read-only `Map`, can the caller cast it back to `MutableMap` and mutate it?It may be runtime-castable since the underlying object is a LinkedHashMap, but doing so violates the contract; treat the result as truly immutable.
- Does `buildSet` deduplicate?Yes — the receiver is a `MutableSet`, so duplicate `add`s collapse, and it preserves insertion order (LinkedHashSet).
saying these in an interview costs you the question
- Saying the return type is `MutableList`/`MutableMap`
- Thinking the receiver is the read-only interface
- Claiming it returns a brand-new copy unrelated to the builder
- Relying on mutating the builder after the block returns
- Assuming buildMap gives unordered output