Kotlin Sequences
A sequence is a lazy collection. Regular collection functions process all items at every step. Sequences wait and process items one at a time from start to finish. For large collections with multiple transformation steps, sequences save memory and run faster.
Collections vs Sequences: The Key Difference
Collection (eager):
listOf(1..1000)
.filter { it % 2 == 0 } ← creates full list of 500 evens
.map { it * 3 } ← creates full list of 500 results
.take(5) ← then picks first 5
Total work: 1000 filter + 500 map = 1500 operations
Sequence (lazy):
(1..1000).asSequence()
.filter { it % 2 == 0 }
.map { it * 3 }
.take(5)
.toList()
Processes item by item. Stops as soon as it has 5 results.
Total work: only ~10 operations
Creating a Sequence
// From a collection
val seq1 = listOf(1, 2, 3, 4, 5).asSequence()
// Using sequenceOf
val seq2 = sequenceOf("A", "B", "C")
// Using generateSequence (infinite)
val naturals = generateSequence(1) { it + 1 } // 1, 2, 3, 4, ...
// Take the first 10 natural numbers
val firstTen = naturals.take(10).toList()
println(firstTen) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]Sequence Pipeline
Item enters the pipeline:
1 → filter(odd?) → YES → map(*10) → 10 → take(3) check
2 → filter(odd?) → NO → (skipped entirely)
3 → filter(odd?) → YES → map(*10) → 30 → take(3) check
4 → filter(odd?) → NO → (skipped)
5 → filter(odd?) → YES → map(*10) → 50 → take(3) DONE
Result: [10, 30, 50]
val result = (1..1000)
.asSequence()
.filter { it % 2 != 0 } // keep odd numbers
.map { it * 10 } // multiply by 10
.take(3) // take only 3
.toList()
println(result) // [10, 30, 50]Intermediate vs Terminal Operations
Intermediate (lazy — do nothing until terminal is called):
filter, map, flatMap, take, drop, sorted, distinct
Terminal (triggers execution and returns a result):
toList(), toSet(), first(), sum(), count(), forEach()
val seq = (1..10).asSequence().filter { it > 5 }
// Nothing has happened yet!
val list = seq.toList() // NOW it processes
println(list) // [6, 7, 8, 9, 10]Infinite Sequences
// Fibonacci using sequence
val fibonacci = generateSequence(Pair(0, 1)) { Pair(it.second, it.first + it.second) }
.map { it.first }
.take(10)
.toList()
println(fibonacci) // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]When to Use Sequences
Use regular collections when:
- Collection has fewer than ~1,000 items
- You apply only 1-2 transformation steps
- Simplicity matters more than performance
Use sequences when:
- Collection is large (thousands or millions of items)
- You chain 3 or more transformation steps
- You use take() to stop processing early
- You work with infinite streams of data
Practical Example: Log File Processor
fun main() {
val logLines = generateSequence(1) { it + 1 }
.map { "[$it] ${if (it % 3 == 0) "ERROR" else "INFO"}: Event #$it" }
// Process only the first 5 ERROR lines without loading everything
val errors = logLines
.filter { it.contains("ERROR") }
.take(5)
.toList()
errors.forEach { println(it) }
}Output:
[3] ERROR: Event #3
[6] ERROR: Event #6
[9] ERROR: Event #9
[12] ERROR: Event #12
[15] ERROR: Event #15The sequence generates log lines infinitely but stops the moment it finds 5 error lines. No unnecessary computation happens beyond that point.
