What is the seedless overload generateSequence { ... }, and when would you use it instead of the seeded form?
answer
- Zero-arg lambda: () -> T?
- Each element from external/mutable state
- null ends it (EOF, empty queue)
- generateSequence { reader.readLine() }
- Pair with use { } for streaming I/O
basics
~20 sIt'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.
solid answer
~40 sThe seedless overload is generateSequence(nextFunction: () -> T?): Sequence<T>. Unlike the seeded form, the lambda takes no argument — every element (including the first) comes from calling nextFunction(), which typically pulls from external/mutable state (an iterator, a reader, a queue). The sequence ends when nextFunction returns null. The classic use is consuming a stateful source: generateSequence { reader.readLine() } reads lines until readLine() returns null at EOF. Use it when each element isn't a pure function of the previous one but comes from side-effecting I/O or an external producer. Because it's lazy, lines are read on demand, which pairs well with bufferedReader().use { } so you can process huge inputs without loading everything into memory. The seeded overload is better when next genuinely depends only on the previous value.
code
kotlin · 5 linesFile("big.log").bufferedReader().use { reader ->
val errorCount = generateSequence { reader.readLine() }
.count { it.contains("ERROR") }
println(errorCount)
}go deeper
Recognizes the seedless form takes a no-arg lambda stopping on null; can read lines with it.
Chooses seeded vs seedless correctly and pairs the seedless form with use { } for streaming.
Reasons about single-pass semantics, capturing mutable state, and EOF/empty-source edge cases.
Weighs lazy streaming vs eager loading for large I/O, thread-safety of captured state, and API choices like poll vs remove.
## Two overloads There are two `generateSequence` functions: ```kotlin // seeded: next depends on the previous element fun <T : Any> generateSequence(seed: T?, nextFunction: (T) -> T?): Sequence<T> // seedless: each call produces the next element from external state fun <T : Any> generateSequence(nextFunction: () -> T?): Sequence<T> ``` The **seedless** overload takes a **zero-argument** lambda. It is called repeatedly; each result becomes the next element, and a returned **`null`** ends the sequence. There is no "previous element" passed in — so the function must obtain each value some other way, usually from **mutable/external state**. ## Canonical use: stateful sources ```kotlin // Read all lines until EOF (readLine() returns null at end of stream) File("big.log").bufferedReader().use { reader -> generateSequence { reader.readLine() } .filter { it.contains("ERROR") } .forEach(::println) } ``` Because the sequence is **lazy** and pull-based, lines are read **one at a time** as the pipeline consumes them. Combined with `use { }` (which closes the reader), you can stream files far larger than memory. Other fits: ```kotlin val it = someIterator() generateSequence { if (it.hasNext()) it.next() else null } // drain a queue generateSequence { queue.poll() } // poll() returns null when empty ``` ## Seeded vs seedless — how to choose - Use **seeded** `generateSequence(seed) { prev -> ... }` when the next value is a **pure function of the previous** one (numeric series, parent-chain walks, Fibonacci with a pair). - Use **seedless** `generateSequence { ... }` when values come from a **side-effecting producer** (I/O, iterators, channels-as-pull, random draws) and there is no meaningful "previous element" to feed forward. ## Gotchas - The lambda is **impure by design** here; capturing mutable state is expected, but the sequence is then **single-pass-ish** — iterating it twice will continue from wherever the source left off (e.g. a reader already at EOF yields nothing). - Don't share one such sequence across threads; the captured state usually isn't thread-safe. - Returning `null` is the *only* stop signal — make sure your source actually yields `null` (e.g. `Queue.poll()` does, but `Queue.remove()` throws instead).
- Why is generateSequence { reader.readLine() } memory-efficient for huge files?It's lazy: each readLine() runs only when the next element is pulled, so at most one line is in flight rather than the whole file.
- What happens if you iterate such a sequence twice?It continues from the source's current state. A reader already at EOF yields an empty sequence the second time; it does not reset.
saying these in an interview costs you the question
- Thinking the seedless lambda receives the previous element
- Forgetting that null is the only termination signal
- Assuming the sequence is replayable when it wraps mutable I/O state
- Not closing the reader (no use { } / try-with-resources)
- Using Queue.remove() (throws) instead of poll() (returns null) as the source