Sequences and Performance
Prerequisites: Collection functions, Inline functions, reified, and value classes
The first rule of performance work: measure before you change anything. Guesses about what's slow are wrong surprisingly often.
Sequences — lazy collection processing
Each step in a list chain (map, filter, ...) builds a whole new
list. A sequence processes elements one at a time through the whole
chain, and stops as soon as it has enough:
fun main() {
val result = listOf(1, 2, 3, 4)
.asSequence()
.map { println("map $it"); it * 2 }
.filter { println("filter $it"); it > 2 }
.first()
println(result)
}
map 1
filter 2
map 2
filter 4
4
Only two elements were processed. With a plain list, all four would be mapped and then all four filtered.
Sequences can even be infinite:
fun main() {
println(generateSequence(1) { it * 2 }.take(8).toList())
println((1..1_000_000).asSequence().map { it * it }.filter { it % 7 == 0 }.take(3).toList())
}
[1, 2, 4, 8, 16, 32, 64, 128]
[49, 196, 441]
Use sequences for large collections with several steps, or when you only need the first few results. For small lists, plain collection functions are usually faster — sequences have per-element overhead.
Measuring
measureTimeMillis { }/measureTime { }— quick checks.- JMH (via the
kotlinx-benchmarkplugin) — reliable micro-benchmarks that account for JVM warm-up. - Profilers — IntelliJ's built-in profiler and Android Studio Profiler show where CPU time and memory actually go.
The JVM compiles hot code while the program runs, so the first runs of any code are slower. Never judge performance from a single cold run.
Common wins
- Get blocking work off the main thread — with coroutines and
Dispatchers.IO, as covered earlier. - Avoid N+1 queries — loading 100 orders and then running one query per order is a classic back-end bottleneck.
- Use the right collection — a
SetorMaplookup instead of searching a list inside a loop turns O(n²) into O(n). - Avoid boxing in hot loops —
List<Int>stores boxedIntegerobjects;IntArraystores raw numbers. - On Android — enable R8 for release builds (it shrinks and optimizes code), and add Baseline Profiles to speed up app startup.
Common mistakes
- Converting every chain to a sequence "for performance" without measuring. For small lists it's usually slower.
- Benchmarking in a debug build or on the first run.