How it works
The explanation, with examples you can run.
Watching the order
fun main() { val words = listOf("kotlin", "is", "a", "language") println("List:") val l = words .filter { println(" filter $it"); it.length > 1 } .map { println(" map $it"); it.uppercase() } .first() println(" -> $l") println("Sequence:") val s = words.asSequence() .filter { println(" filter $it"); it.length > 1 } .map { println(" map $it"); it.uppercase() } .first() println(" -> $s")}The list version filters all four words, maps the three survivors, then takes the first: seven lambda calls and two temporary lists. The sequence version filters kotlin, maps it, and first() is satisfied: two calls and no temporary lists. The output shows "horizontal" (step by step) against "vertical" (element by element) evaluation.
Making sequences
list.asSequence()wraps an existing collection.generateSequence(seed) { next(it) }produces values until the function returnsnull, possibly forever.sequence { yield(x); yieldAll(other) }builds one with ordinary code. It uses a restricted suspend function under the hood:yieldpauses the builder until the consumer asks for the next value, the same machinery as coroutines, but on one thread.
val fibonacci = sequence { var a = 0L; var b = 1L while (true) { yield(a); val t = a + b; a = b; b = t }} fun main() = println(fibonacci.take(10).toList()) // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]Terminal and intermediate
Intermediate operations return another Sequence (map, filter, take, distinct). Terminal ones produce something else and do the work (toList, first, count, sum, forEach). Some intermediate operations are stateful: sorted() and distinct() must see (or remember) many elements, so sorted() on a sequence reads everything first. A sequence can usually be iterated more than once (each pass starts over), but generateSequence { ... } without a seed and iterator.asSequence() can only be consumed once.
Choosing
Collection operations are inline, so a single filter on a list compiles to a tight loop. Sequence operations are regular functions with lambda objects and an iterator per stage. Rules of thumb:
- List: small or medium data, one or two steps, or you need the intermediate results anyway.
- Sequence: long chains on large data,
first/take/anythat can stop early, data that is generated, or would not fit in memory as a list.
Measure if it matters. Java's Stream is a third option on the JVM, with parallel streams; a Sequence is single-threaded and works on every Kotlin target. For values that arrive over time, asynchronously, the coroutine equivalent of a sequence is a Flow.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.