skip to content

Sequence vs Collection Performance

Sequences win on long chains, large or infinite data, and anything that short-circuits with first or take; for small collections the eager operators are usually faster. Interviewers want the reasoning, not a blanket rule.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

What is the difference between a List and a Sequence in Kotlin, and when would you choose a Sequence for performance?

level: juniorimportance: must knowfreq 80%

answer

  1. Eager = horizontal, new list per step
  2. Lazy = vertical, one element through whole chain
  3. asSequence() + terminal op
  4. Long chain + big data + short-circuit -> Sequence
  5. Small/single op -> eager wins

basics

~20 s

A List runs each step fully and makes a new list every step. A Sequence is lazy: it pulls one element all the way through the chain. Use Sequences for long chains over large data.

solid answer

~40 s

Collection operators (`map`, `filter`) on a `List` are eager: each call processes the whole collection and allocates a new intermediate `List`. A `Sequence` is lazy: nothing runs until a terminal operation (`toList`, `first`, `count`), then elements flow one at a time through the whole chain, so no intermediate lists are allocated. You convert with `.asSequence()`. Sequences win when you chain several operators over large data, or want short-circuiting (`first`, `take`, `find`) so you stop early. For small collections or a single operation, eager `List` ops are usually faster because Sequences add per-element iterator/lambda overhead. Rule of thumb: many steps + large/infinite data + early exit favors `Sequence`; small data or one step favors eager collection operators.

code

kotlin · 6 lines
kotlin
val nums = (1..1_000_000).toList()
val result = nums.asSequence()
    .filter { it % 2 == 0 }
    .map { it * it }
    .take(5)
    .toList() // terminal op triggers the lazy pipeline

go deeper

for a junior

Knows a Sequence is lazy and a List is eager, and can convert with asSequence().

for a middle

Explains intermediate vs terminal ops and why long chains over large data favor Sequences.

for a senior

Quantifies allocation and short-circuit savings and names cases where eager is faster.

for a principal

Frames it as a profiling/measurement decision and warns against premature Sequence use on small data.

## The two execution models Kotlin's standard-library transformation operators come in two flavors that look identical but execute very differently. - **Eager (Iterable/Collection):** Operators like `map`, `filter`, `sorted` defined on `Iterable`/`List` are **horizontal**. Each operator processes the *entire* collection and returns a brand-new `List`. A chain of N operators allocates N intermediate lists. - **Lazy (Sequence):** The same-named operators defined on `Sequence` are **vertical** and lazy. They build a pipeline but do nothing until a **terminal operation** runs. Then each element is pulled through *every* step before the next element starts. No intermediate lists are created. ## Intermediate vs terminal operations - **Intermediate** operations (`map`, `filter`, `take`, `distinct`) return another `Sequence` and are lazy. - **Terminal** operations (`toList`, `first`, `count`, `sum`, `forEach`, `fold`) trigger the actual work and produce a non-sequence result. ## Why this matters for performance ```kotlin val list = (1..1_000_000).toList() // Eager: builds a 1M filtered list, then a 1M mapped list, then takes 10 val eager = list.filter { it % 2 == 0 }.map { it * 2 }.take(10) // Lazy: pulls elements until 10 pass; touches ~20 elements total val lazy = list.asSequence().filter { it % 2 == 0 }.map { it * 2 }.take(10).toList() ``` The eager version does ~2 million operations and allocates two big lists. The lazy version short-circuits after producing 10 results. ## When Sequences win - **Long chains:** Several `map`/`filter` steps — you avoid allocating an intermediate list per step. - **Large or infinite data:** `generateSequence` can model infinite streams; eager ops cannot. - **Short-circuiting:** `first`, `find`, `take`, `any`, `none` stop as soon as the answer is known. ## When eager collections win - **Small collections:** Iterator and lambda-dispatch overhead per element outweighs allocation savings. - **Single operation:** One `map` on a `List` is just a tight loop; wrapping in a `Sequence` adds indirection. - **Operations needing the whole dataset:** `sorted`, `groupingBy` buffer everything anyway, so laziness gives little. ## How to convert Use `list.asSequence()` to go lazy, and a terminal op (often `.toList()`) to materialize. Always end a `Sequence` chain with a terminal operation; an intermediate-only chain does nothing.

  • What happens if you build a Sequence chain but never call a terminal operation?
    Nothing executes — intermediate operations are lazy, so the chain stores transformations but produces no result until a terminal op like toList/first/count runs.
  • Does asSequence() copy the underlying list?
    No. It wraps the existing iterable with a lazy view; elements are read on demand, not copied.

Eager is an assembly line that finishes every station for all parts before moving on; a Sequence sends one part through every station, then the next.

saying these in an interview costs you the question

  • Claiming Sequences are always faster than Lists
  • Thinking map on a List is lazy
  • Forgetting the terminal operation, expecting work to happen
  • Believing asSequence() copies all elements upfront
  • Confusing Sequence with Java parallel Stream

context

open as a page

How does short-circuiting work with Sequences, and why does `asSequence().filter{}.first{}` avoid scanning the whole collection?

level: middleimportance: must knowfreq 65%

basics

~10 s

A Sequence pulls elements one by one. Terminal operations like first, find, take, and any stop the moment they have their answer, so the rest of the data is never touched.

open as a page

Why can converting a small list to a Sequence actually be slower, and how would you decide whether a Sequence is worth it?

level: middleimportance: should knowfreq 55%

basics

~10 s

Sequences add overhead per element: iterator objects and lambda calls for every step. On small lists or a single operation, that overhead beats the savings, so plain List operators are faster.

open as a page

Analyze allocation and pass count for a multi-step pipeline run eagerly vs as a Sequence over large data. What exactly differs?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Eager makes a new list at every step and walks the whole data once per step. A Sequence makes no intermediate lists and walks each element once through all steps, stopping early if a terminal allows.

open as a page

You own a shared data-processing library. How do you decide your public API returns List vs Sequence, and what pitfalls do you warn callers about?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

Return a List when results are small, finite, and reused; return a Sequence when data may be large or infinite and callers can stream and stop early. Warn that Sequences are lazy and may be single-use.

open as a page