Sequences and Lazy Evaluation

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.

Horizontal versus vertical processing, then a timed comparison (Lazy.kt)Kotlin
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() }
}
Output
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.