skip to content

How does the iterator{} builder relate to sequence{}, and when would you reach for sequence{} over a plain List or generateSequence?

level: seniorimportance: nice to knowfreq 18%

answer

  1. iterator{} == same API, returns Iterator<T>
  2. sequence{} ≈ Sequence { iterator(block) }
  3. sequence{} for branchy/recursive/yieldAll logic
  4. generateSequence for simple seed→next unfolds
  5. List for small finite reused data

basics

~10 s

iterator{} works just like sequence{} but returns an Iterator instead of a Sequence. Use sequence{} when generation logic is branchy or recursive, or when the stream is infinite/expensive and you want laziness.

solid answer

~40 s

Kotlin offers a twin builder `iterator { ... }` that uses the same `yield`/`yieldAll` API and the same restricted-suspension machinery, but returns an `Iterator<T>` instead of a `Sequence<T>`. In fact `sequence { block }` is essentially `Sequence { iterator(block) }` — a Sequence whose iterator() re-invokes the builder. Reach for `sequence {}` (over building a `List`) when: the data is infinite or large and you want on-demand, memory-bounded production; the emission logic is **imperative/branchy** (conditionals, nested loops, early `return@sequence`) where `generateSequence`'s single 'next' lambda is awkward; or you need to **splice** sub-sequences with `yieldAll` (e.g. recursive tree traversal). Prefer `generateSequence(seed){next}` for simple unfold-style streams. Prefer a `List` when the set is small, finite, and consumed multiple times — sequences add per-element overhead and recompute on each pass.

go deeper

for a junior

Knows sequence{} returns a lazy Sequence and List is the eager alternative.

for a middle

Can pick sequence{} vs generateSequence vs List for a given shape of data.

for a senior

Explains the iterator{} twin and the sequence ≈ Sequence{iterator(block)} relationship and trade-offs.

for a principal

Weighs allocation, recomputation, and operator-chain cost to choose the right abstraction in performance-sensitive code.

## The iterator{} twin The standard library has two builders sharing one API: - `sequence { ... } : Sequence<T>` - `iterator { ... } : Iterator<T>` Both give you a `SequenceScope<T>` receiver with `yield`/`yieldAll`, both are restricted coroutines. The relationship: ```kotlin // conceptually public fun <T> sequence(block: suspend SequenceScope<T>.() -> Unit): Sequence<T> = Sequence { iterator(block) } ``` So a `sequence {}` is just a `Sequence` whose `iterator()` calls `iterator(block)`. That is also *why* each iteration restarts the block (new iterator each time). Use `iterator {}` directly when an API specifically needs an `Iterator` (e.g. implementing `Iterable.iterator()`); otherwise `sequence {}` is more composable because `Sequence` carries the full lazy operator chain (`map`, `filter`, `take`, …). ```kotlin class Ring<T>(val items: List<T>) : Iterable<T> { override fun iterator() = iterator { while (true) yieldAll(items) // infinite cycling iterator } } ``` ## sequence{} vs generateSequence ```kotlin // generateSequence: functional unfold val powers = generateSequence(1) { it * 2 } // sequence: imperative, branchy, can yieldAll val interleaved = sequence { for (i in 1..3) { yield(i) if (i % 2 == 0) yieldAll(listOf(-i, -i)) // branch + splice } } ``` Choose `generateSequence` for clean seed→next unfolds (and its nullable-`next` stop condition). Choose `sequence {}` when control flow is irregular, recursive, or needs `yieldAll`. ## sequence{} vs List | Use a List when | Use a sequence when | |---|---| | Small, finite data | Large/infinite or unknown size | | Consumed many times | Consumed once, lazily | | Need random access / size | Streaming, memory-bounded | | Few transformation steps | Long operator chain (avoid intermediate lists) | Sequences avoid materializing intermediate collections in a `map.filter.take` chain, but add per-element call overhead and **recompute** on each pass — so for short chains over small data, a `List` (eager) is usually faster and simpler. ## Summary `iterator{}` = same generator, `Iterator` result. `sequence{}` = the composable lazy form. Pick it over `List` for laziness/infinity, over `generateSequence` for branchy/recursive emission.

  • Why does each iteration of a sequence{} restart the block?
    Because sequence{} is a Sequence whose iterator() calls iterator(block) anew each time, producing a fresh state machine per iteration.
  • When is generateSequence cleaner than sequence{}?
    For simple unfolds: a seed and a pure next() function, optionally returning null to stop. sequence{} wins when emission has branches, nested loops, or yieldAll splicing.

saying these in an interview costs you the question

  • Not knowing iterator{} exists or how it relates to sequence{}
  • Using sequence{} for tiny finite data consumed repeatedly
  • Claiming sequences are always faster than Lists
  • Forcing branchy logic into generateSequence's single lambda when sequence{} fits better

context