What does generateSequence(seed) { next } do, and how do you stop it from producing values forever?
answer
- Seed is the first element
- next(prev) produces each element
- Returning null stops it (null not emitted)
- Infinite unless you stop or take()
- Lazy: terminal op drives evaluation
basics
~10 sIt makes a lazy stream that starts at the seed and repeatedly applies your function to get the next value. It stops when your function returns null. Until then, it never ends.
solid answer
~40 sgenerateSequence(seed) { next } returns a Sequence<T> built lazily: the first element is the seed, and each subsequent element is produced by calling the lambda with the previous element. The sequence terminates the first time the lambda returns null (that null is not emitted). Because it is a Sequence, nothing runs until a terminal operation (toList, first, sum, etc.) pulls elements, and elements are computed one at a time on demand. To bound an otherwise infinite sequence you either return null from the generator at the stop condition, or use take(n)/takeWhile { }/first { } on the consumer side. Example: generateSequence(1) { it * 2 }.take(5).toList() yields [1, 2, 4, 8, 16].
code
kotlin · 5 linesval powers = generateSequence(1) { (it * 2).takeIf { v -> v <= 100 } }
println(powers.toList()) // [1, 2, 4, 8, 16, 32, 64]
val naturals = generateSequence(1) { it + 1 }
println(naturals.take(5).toList()) // [1, 2, 3, 4, 5]go deeper
Knows seed is first element, next produces the rest, and null stops it; can bound with take.
Explains laziness and that terminal ops drive evaluation; uses takeWhile/takeIf to stop cleanly.
Discusses infinite-sequence hazards, short-circuiting terminals, and operator fusion per element.
Frames when a lazy generator beats eager collection building and reasons about purity/cost of nextFunction in hot paths.
## What it is `generateSequence` is a top-level function in the Kotlin standard library that builds a **`Sequence<T>`** — a *lazy*, pull-based collection. "Lazy" means no value is computed until something downstream actually asks for it (a *terminal* operation like `toList()`, `first()`, `sum()`, `forEach`). ## The seeded overload ```kotlin fun <T : Any> generateSequence(seed: T?, nextFunction: (T) -> T?): Sequence<T> ``` - The **first element** is `seed`. - Each following element is `nextFunction(previousElement)`. - The sequence **stops** the moment `nextFunction` returns `null`. That `null` is the terminator and is **not** included. - Note the types: `seed` is `T?` and `nextFunction` returns `T?`. If `seed` is `null`, the sequence is empty. ```kotlin // powers of two until they exceed 100 generateSequence(1) { prev -> (prev * 2).takeIf { it <= 100 } } .toList() // [1, 2, 4, 8, 16, 32, 64] ``` ## Infinite by default If `nextFunction` never returns `null`, the sequence is **infinite**: ```kotlin val naturals = generateSequence(1) { it + 1 } // 1, 2, 3, ... ``` Calling a terminal that consumes everything (e.g. `toList()`, `count()`) on an infinite sequence loops forever / overflows. You must bound it on the consumer side with **`take(n)`**, **`takeWhile { }`**, **`first()`/`first { }`**, or **`find { }`** — these are short-circuiting and stop pulling once satisfied. ```kotlin naturals.take(5).toList() // [1, 2, 3, 4, 5] naturals.first { it % 7 == 0 } // 7 ``` ## Laziness in practice Intermediate operators (`map`, `filter`, `take`) are also lazy and fuse element-by-element. Nothing executes at the call site: ```kotlin val s = generateSequence(1) { it + 1 } .map { println("map $it"); it * it } // nothing printed yet val r = s.take(3).toList() // NOW it runs: map 1, map 2, map 3 ``` ## Common idioms - Walk a parent chain (e.g. exceptions, tree nodes): `generateSequence(node) { it.parent }`. - Read lines until EOF: `generateSequence { reader.readLine() }` (seedless overload — see below). - Numeric series with a stop condition via `takeIf`. Keep `nextFunction` **pure and cheap**; it is invoked once per produced element during iteration.
- Is the null that stops the sequence included as an element?No. The null acts purely as a terminator; the last emitted element is the last non-null value the function returned.
- What happens if seed is null?The sequence is empty — iteration ends immediately because the terminator condition is met before any element is produced.
Like a snowball rolling downhill: you give it a starting size (seed), and each push (next) makes the next size — it keeps rolling until you decide to stop it (null or take).
saying these in an interview costs you the question
- Saying it returns a List eagerly
- Thinking the stopping null becomes the last element
- Calling toList() on an infinite sequence and not seeing the hang problem
- Believing map/filter run immediately at the call site
- Confusing the stop condition (null) with throwing an exception