skip to content

generateSequence

generateSequence produces values from a seed and a next function, stopping when that function returns null, and the seedless form gives you an unbounded stream. It is the idiomatic way to model pagination or repeated polling as a sequence.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

What does generateSequence(seed) { next } do, and how do you stop it from producing values forever?

level: juniorimportance: must knowfreq 55%

answer

  1. Seed is the first element
  2. next(prev) produces each element
  3. Returning null stops it (null not emitted)
  4. Infinite unless you stop or take()
  5. Lazy: terminal op drives evaluation

basics

~10 s

It 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 s

generateSequence(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 lines
kotlin
val 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

for a junior

Knows seed is first element, next produces the rest, and null stops it; can bound with take.

for a middle

Explains laziness and that terminal ops drive evaluation; uses takeWhile/takeIf to stop cleanly.

for a senior

Discusses infinite-sequence hazards, short-circuiting terminals, and operator fusion per element.

for a principal

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

context

open as a page

How would you generate a Fibonacci sequence with generateSequence, and what does this reveal about carrying state in the seeded overload?

level: middleimportance: should knowfreq 35%

basics

~20 s

Because each Fibonacci number needs the two before it, you make the element a pair (a, b). The seed is the first pair, and next turns (a, b) into (b, a+b). Then map each pair to its first number.

open as a page

What is the seedless overload generateSequence { ... }, and when would you use it instead of the seeded form?

level: middleimportance: should knowfreq 40%

basics

~20 s

It's a version where you give just one function that produces each next value (using outside state), and it stops when that function returns null. Great for reading until there's nothing left, like lines from a file.

open as a page

When is generateSequence the right tool versus building a list eagerly or writing a manual loop, and what performance/correctness traits should drive that choice?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Use generateSequence when the data is unbounded or you only need part of it, so you don't compute or store more than necessary. If you need the whole finite set and will reuse it many times, an eager list can be simpler and faster.

open as a page

Design a generateSequence-based pipeline to consume a cursor-paginated API until exhaustion, and explain the laziness and termination guarantees you rely on.

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Make each element a page. Start by fetching the first page (seed), then next fetches the page after the current one using its cursor, returning null when there's no next page. Flatten the pages into items and take what you need.

open as a page