A list pipeline is eager: each step runs over every element and allocates a new list. After asSequence() the same operators are lazy: nothing runs until a terminal operation (toList, sum, first) pulls elements one at a time through every step. The trace marks each filter and map call with f and m; then both styles are timed over three million books.
import kotlin.time.measureTime
val big = List(3_000_000) { books[it % books.size].copy(id = it) } // 3 million books
val bigSeq = big.asSequence() // a lazy view of it
fun bench(name: String, work: () -> Any) {
repeat(3) { work() } // let the JIT warm up
val us = List(7) { measureTime { work() }.inWholeMicroseconds }.sorted()
println("%-15s median %,9d us".format(name, us[3]))
}
fun main() {
val a = books.filter { print("f${it.id} "); it.price < 20 }
.map { print("m${it.id} "); it.title }.take(2)
println("list -> $a")
val b = books.asSequence().filter { print("f${it.id} "); it.price < 20 }
.map { print("m${it.id} "); it.title }.take(2).toList()
println("seq -> $b")
bench("list, all") { big.filter { it.inStock }.map { it.price * 1.08 }.sum() }
bench("sequence, all") { bigSeq.filter { it.inStock }.map { it.price * 1.08 }.sum() }
bench("list, first") { big.filter { it.inStock }.map { it.price * 1.08 }.first() }
bench("sequence, first") { bigSeq.filter { it.inStock }.map { it.price * 1.08 }.first() }
}f1 f2 f3 f4 f5 f6 m1 m4 m5 list -> [The Quiet Harbor, Small Steps to Big Summits] f1 m1 f2 f3 f4 m4 seq -> [The Quiet Harbor, Small Steps to Big Summits] list, all median 233,828 us sequence, all median 42,817 us list, first median 216,400 us sequence, first median 2 us
The list filters all six books, then maps three; the sequence carries each book through both steps and stops after the second match. The timings were measured on this machine (Core i9-7980XE, OpenJDK 21.0.12 10,925 , WSL2 6 , other builds running, so they varied between runs): the full pass ran about five times faster as a sequence, which builds no intermediate lists, and first() touched one element instead of three million.
Each step adds an iterator and a lambda that cannot be inlined, so keep plain lists for small collections, as the Kotlin documentation advises. Choose a sequence for large data, long chains, or a short-circuiting end such as first or take; a stateful step such as sortedBy still consumes everything. generateSequence(1) { it * 2 } and the sequence { yield(x) } builder create infinite sequences.