What does the sequence{} builder do in Kotlin, and how do you emit elements from inside it?
answer
- SequenceScope receiver → yield / yieldAll
- Lazy: nothing runs until a terminal op pulls
- yield suspends producer until next request
- @RestrictsSuspension — no delay() inside
- Returns Sequence<T>, not a List
basics
~10 ssequence{} creates a lazy sequence. Inside the block you call yield(x) to hand out one value at a time, only producing them as the consumer asks for them.
solid answer
~40 sThe sequence{} builder is a standard-library function returning a Sequence<T> whose elements are produced lazily by a block of code. Inside that block you have a SequenceScope receiver, so you can call yield(value) to emit a single element and yieldAll(iterable/sequence/iterator) to emit many at once. Nothing in the block runs until a terminal operation (toList(), forEach, first(), etc.) starts pulling. Each yield suspends the producer until the consumer requests the next item, giving pull-based, on-demand generation. Because it is lazy you can express infinite or expensive streams safely. The builder is backed by Kotlin coroutine machinery (restricted suspension), but you don't need any coroutine imports or a CoroutineScope to use it.
code
kotlin · 8 linesval fib = sequence {
var a = 0; var b = 1
while (true) {
yield(a)
val next = a + b; a = b; b = next
}
}
println(fib.take(6).toList()) // [0, 1, 1, 2, 3, 5]go deeper
Knows sequence{} is lazy and that yield emits one element at a time.
Adds yieldAll, the SequenceScope receiver, and that terminal ops trigger execution.
Explains pull-based suspension and when to prefer sequence{} over generateSequence.
Frames it against restricted suspension and the cost model of lazy pipelines in hot paths.
## What sequence{} is `sequence { ... }` is a top-level function in the Kotlin standard library that builds a **`Sequence<T>`** — a lazily evaluated stream of elements — from an imperative block of code. A `Sequence` is the lazy cousin of a `List`: it does not store all its elements; it computes them **on demand** as a consumer iterates. ## How you emit elements The lambda you pass to `sequence {}` runs with a **receiver** of type `SequenceScope<T>`. That receiver gives you two suspending functions: - **`yield(value: T)`** — emits exactly **one** element, then pauses the producer until the next element is requested. - **`yieldAll(...)`** — emits **many** elements; overloads accept an `Iterable<T>`, a `Sequence<T>`, or an `Iterator<T>`. ```kotlin val nums: Sequence<Int> = sequence { yield(1) // emit one yieldAll(listOf(2, 3)) // emit many yield(4) } println(nums.toList()) // [1, 2, 3, 4] ``` ## Lazy / pull-based execution Nothing in the block executes when you call `sequence {}`. Execution starts only when a **terminal operation** begins consuming — e.g. `toList()`, `forEach`, `first()`, `sum()`, or a `for` loop. Each call to `yield` **suspends** the producer; control returns to the producer to compute the next value only when the consumer asks for it. This is why an infinite producer is safe as long as the consumer stops early: ```kotlin val evens = sequence { var n = 0 while (true) { yield(n); n += 2 } // never-ending } println(evens.take(3).toList()) // [0, 2, 4] — only 3 values produced ``` ## Under the hood (restricted suspension) The builder is implemented with coroutines, but a **restricted** form: `SequenceScope` is annotated `@RestrictsSuspension`, so inside the block you may call only its own suspend members (`yield`, `yieldAll`). You **cannot** call arbitrary `suspend` functions like `delay()` — sequences are synchronous and single-threaded. You don't import any coroutine package or supply a dispatcher to use `sequence {}`. ## sequence{} vs generateSequence `sequence {}` is imperative (you write `yield` calls in control flow). `generateSequence(seed) { next }` is functional — you give a seed and a function that returns the next element (or `null` to stop). Use `sequence {}` when emission logic is branchy or needs `yieldAll`.
- When does the code inside sequence{} actually start running?Only when a terminal operation (toList, first, forEach, a for-loop, etc.) begins consuming the sequence — never at the moment sequence{} is called.
- Can you build an infinite sequence with it?Yes — an unbounded loop with yield is fine as long as the consumer takes a finite prefix (take, first, etc.); the producer only runs as far as needed.
Like a vending machine: it only drops the next snack when you press the button, not all of them at once.
saying these in an interview costs you the question
- Thinking sequence{} eagerly builds a List in memory
- Saying you need a CoroutineScope or runBlocking to use it
- Believing the block runs immediately when sequence{} is called
- Confusing yield (one item) with yieldAll (many items)