Sequences (Lazy Collections)
A sequence is a lazily-evaluated collection that processes elements one at a time through an entire operation chain, only doing work when a terminal operation finally pulls the results.
Learn Sequences (Lazy Collections) in our free Kotlin course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…
Part of the free Kotlin course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
You'll learn asSequence() , the difference between lazy and eager evaluation, generateSequence , terminal operations, and exactly when sequences beat plain lists.
What You'll Learn in This Lesson
1️⃣ Eager Lists: Each Step Finishes First
Ordinary collection operations are eager . When you chain .map {' '}.filter {' '} on a list, map runs over every element and builds a whole new list, then filter runs over that. The print statements below reveal the order: all the maps, then all the filters.
Every map runs before any filter , and an intermediate list of all four doubled values exists in memory in between.
2️⃣ Sequences: One Element All the Way Through
Call .asSequence() and the same chain becomes lazy . Now each element travels through the entire pipeline before the next one starts, and no work happens at all until a terminal operation such as toList() pulls the values. Watch how map and filter now interleave.
No intermediate list is built, and because elements flow one at a time, a sequence can stop early — first {' '} would halt as soon as it found a match instead of transforming everything first.
3️⃣ generateSequence: Infinite, but Lazy
Because sequences only compute what's consumed, they can be infinite . generateSequence(seed) {' '} starts from a seed and repeatedly applies a function. Pair it with take(n) and a terminal operation to get exactly the values you need and not one more.
Without laziness, an infinite source like this could never finish. With it, take(5) computes precisely five values.
Your turn. Fill in the ___ , then run and compare.
Combine generateSequence with a lazy filter/map chain and an early take .
📋 Quick Reference — Sequences
Practice quiz
How is a List chain evaluated?
- Lazily, one element at a time
- Only when printed
- Eagerly: each operation completes and builds a new list before the next
- In random order
Answer: Eagerly: each operation completes and builds a new list before the next. List operations are eager; each step builds a full intermediate list before the next runs.
How is a Sequence evaluated?
- Lazily: nothing runs until a terminal operation pulls values
- Eagerly, like a list
- All at once at declaration
- Never
Answer: Lazily: nothing runs until a terminal operation pulls values. A sequence is lazy; work happens only when a terminal operation requests results.
In a sequence chain, how does each element move through the operations?
- All elements finish map, then all finish filter
- Elements are processed in parallel
- Only the first element is processed
- One element flows through the whole chain before the next starts
Answer: One element flows through the whole chain before the next starts. In a sequence each element travels through the entire pipeline one at a time.
Which converts a list into a lazy sequence?
- toList()
- asSequence()
- toSequence()
- lazy()
Answer: asSequence(). asSequence() turns a collection into a lazily-evaluated sequence.
What happens if a sequence chain has no terminal operation?
- No work happens at all
- It runs immediately
- It throws an exception
- It loops forever
Answer: No work happens at all. Without a terminal operation, intermediate operations never execute.
Which of these is a terminal operation?
- map
- filter
- toList
- take
Answer: toList. toList (along with sum, first, count, forEach) is terminal; map/filter/take are intermediate.
What does generateSequence(1) { it * 2 } produce?
- A finite list of two values
- A lazy, conceptually infinite sequence: 1, 2, 4, 8, ...
- A compile error
- Only the number 1
Answer: A lazy, conceptually infinite sequence: 1, 2, 4, 8, .... generateSequence builds a lazy, potentially infinite sequence from a seed and a next function.
Why must you usually pair an infinite generateSequence with take(n)?
- take makes it faster
- take is required syntactically
- To make it eager
- Otherwise a terminal like toList() never finishes
Answer: Otherwise a terminal like toList() never finishes. Limiting with take(n) prevents a terminal operation from running forever on an infinite source.
When do sequences typically outperform plain list operations?
- For very short chains on tiny lists
- For long chains, large data, or an early exit
- Always, in every case
- Only when printing
Answer: For long chains, large data, or an early exit. Laziness pays off with long chains, big data, and early exits that avoid extra work.
What is the relationship between a list chain and the equivalent sequence chain's final result?
- The sequence gives a different answer
- The list is always wrong
- They produce identical results; only the timing and memory differ
- The sequence drops elements
Answer: They produce identical results; only the timing and memory differ. Both produce the same final result; sequences just avoid intermediate collections.