What does asSequence() do to a collection chain, and how does processing differ from calling map/filter directly on a List?
answer
- List = eager, new list each step (stage-at-a-time)
- Sequence = lazy, element-at-a-time through whole chain
- asSequence() opts in; toList() opts out
- Nothing runs until a terminal op pulls
- first()/take short-circuit on a sequence
basics
~10 sasSequence() makes the chain lazy. Instead of building a new list after each map/filter, each element flows through every step one at a time, only when a result is finally needed.
solid answer
~40 sOn a List, every operator (map, filter, take) is eager: it runs over the whole collection and returns a brand-new List, so a 3-step chain allocates 3 intermediate lists. asSequence() converts the Iterable into a Sequence<T>, where intermediate operators are lazy and just wrap the previous step. Nothing runs until a terminal operation (toList, first, sum, forEach, count) pulls elements. Then each element is pushed through the entire pipeline before the next element starts — element-at-a-time, not stage-at-a-time. This avoids intermediate list allocations and lets short-circuiting terminals like first or take stop early without processing the rest. You opt back to a List with toList()/toSet().
code
kotlin · 15 linesval nums = listOf(1, 2, 3, 4)
// Eager: prints all filter results, then all map results
nums.filter { print("f$it "); it % 2 == 0 }
.map { print("m$it "); it * 10 }
// f1 f2 f3 f4 m2 m4
println()
// Lazy: interleaved, element-at-a-time
nums.asSequence()
.filter { print("f$it "); it % 2 == 0 }
.map { print("m$it "); it * 10 }
.toList()
// f1 f2 m2 f3 f4 m4go deeper
Knows asSequence() makes things lazy and that a terminal op is needed to run anything.
Can explain stage-at-a-time (List) vs element-at-a-time (Sequence) and intermediate-vs-terminal distinction.
Articulates intermediate list allocations avoided and short-circuiting via first/take, and when laziness is actually worth it.
Frames it as a streaming/pull model and reasons about its cost/benefit across realistic data sizes and chain shapes.
## The two evaluation models Kotlin gives you two ways to run a transformation pipeline: - **Eager (collections / Iterable):** Calling `map`, `filter`, `take` etc. directly on a `List` runs **immediately** and returns a **new `List`** at every step. This is *stage-at-a-time*: the whole collection goes through stage 1, producing a full intermediate list; that whole list goes through stage 2, producing another full list; and so on. - **Lazy (`Sequence<T>`):** `asSequence()` turns an `Iterable` into a `Sequence`. Intermediate operators (`map`, `filter`, …) **don't run** — they just return a wrapper sequence describing the work. Execution only happens when a **terminal operation** (`toList`, `first`, `sum`, `count`, `forEach`, `find`) asks for results. Then it runs *element-at-a-time*: one element is pulled and pushed through **the entire chain** before the next element is touched. ## Why it matters ```kotlin val list = (1..1_000_000).toList() // EAGER: allocates a full filtered list, then a full mapped list val a = list.filter { it % 2 == 0 }.map { it * 2 }.first() // LAZY: pulls 1, fails filter; pulls 2, passes, maps to 4, first() stops. Two elements touched. val b = list.asSequence().filter { it % 2 == 0 }.map { it * 2 }.first() ``` The eager version builds two million-ish-element intermediate lists just to grab the first result. The sequence version short-circuits after the second element because `first()` only needs one value and the laziness lets earlier stages stop pulling. ## Key APIs / keywords - `asSequence()` — opt in to laziness from any `Iterable`/`Array`/`Map`. - `Sequence<T>` interface with a single `iterator()`. - **Intermediate** operators return `Sequence<T>` (lazy): `map`, `filter`, `take`, `drop`, `distinct`. - **Terminal** operators trigger evaluation and return a concrete value: `toList`, `toSet`, `first`, `sum`, `count`, `forEach`. - `toList()` / `toSet()` — go back from a lazy `Sequence` to an eager collection. ## Mental model A `Sequence` is a recipe, not a meal. `asSequence()` writes the recipe lazily; the terminal operation cooks it, one ingredient pushed all the way through before the next.
- If you call asSequence().map { ... } and never add a terminal operation, does the map lambda run?No. Intermediate operators are lazy; with no terminal op nothing is pulled, so the lambda never executes.
- How do you turn the sequence back into a List?Call a terminal collector like toList() or toSet() (or toMutableList()).
A List pipeline is an assembly line that finishes each station for the whole batch; a Sequence carries one part through every station before the next part starts.
saying these in an interview costs you the question
- Saying map/filter on a Sequence run immediately like on a List
- Claiming sequences always allocate intermediate lists
- Forgetting that a terminal operation is required to trigger work
- Thinking asSequence() copies the data into a new collection eagerly