Skip to content
AgileLogics
6 min read

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-benchmark plugin) — 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

  1. Get blocking work off the main thread — with coroutines and Dispatchers.IO, as covered earlier.
  2. Avoid N+1 queries — loading 100 orders and then running one query per order is a classic back-end bottleneck.
  3. Use the right collection — a Set or Map lookup instead of searching a list inside a loop turns O(n²) into O(n).
  4. Avoid boxing in hot loops — List<Int> stores boxed Integer objects; IntArray stores raw numbers.
  5. 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.