What are iterators in Rust and how does lazy evaluation work?
Learn how Rust iterators work and why lazy evaluation means map and filter do no work until a consumer runs, with examples for interviews.
Expected Interview Answer
An iterator in Rust is any type implementing the Iterator trait, which produces items one at a time through its next method, and lazy evaluation means adapter methods like map and filter build up a computation that does no work until a consuming method actually pulls values.
Iterator adapters such as map, filter, and take return new iterator types without touching the underlying data; nothing runs until a consumer like collect, sum, for, or count calls next. This lets you chain transformations that fuse into a single pass, avoid intermediate allocations, and even work over infinite sequences by only computing the elements that are demanded.
- No work happens until a consumer drives the chain
- Chained adapters fuse into a single efficient pass
- Avoids allocating intermediate collections
- Supports infinite or unbounded sequences safely
- Often compiles down to code as fast as a hand-written loop
AI Mentor Explanation
Lining up your batting order does not score any runs; it just plans who comes next. An iterator chain is that batting card: filter picks eligible players and map assigns roles, but no one walks out until a consumer, like the umpire calling play, demands the next batter. Only then does the first player actually take strike, and the rest wait lazily in the pavilion.
Step-by-Step Explanation
Step 1
Get an iterator
Call iter, into_iter, or iter_mut on a collection, or use a source like a range.
Step 2
Chain adapters
Apply lazy adapters such as map, filter, take, and enumerate that return new iterators.
Step 3
Understand nothing runs yet
At this point no elements have been touched; the chain is only a description.
Step 4
Add a consumer
Call a consuming method like collect, sum, count, or use a for loop to drive next.
Step 5
Values are pulled lazily
Each call to next pulls one element through the whole chain in a single fused pass.
What Interviewer Expects
- Knowing the Iterator trait centers on next returning Option<Item>
- Distinguishing lazy adapters from eager consumers
- Explaining that no work happens until a consumer runs
- Awareness of zero-cost abstraction and loop-equivalent performance
- Recognizing iterators can model infinite sequences
Common Mistakes
- Thinking map or filter execute immediately
- Forgetting a consumer is required and getting an unused iterator warning
- Believing each adapter allocates an intermediate collection
- Confusing iter (references) with into_iter (owned values)
- Assuming iterators are slower than manual loops
Best Answer (HR Friendly)
“Iterators in Rust are a standard way to step through a sequence one item at a time. They are lazy, meaning describing transformations like map or filter does no actual work until you ask for the results, which keeps the code both expressive and fast.”
Code Example
let nums = vec![1, 2, 3, 4, 5, 6];
// These adapters do NOT run yet - the chain is just a plan
let pipeline = nums.iter()
.filter(|&&n| n % 2 == 0)
.map(|&n| n * n);
// The consumer drives the whole chain in one pass
let squares: Vec<i32> = pipeline.collect();
println!("{squares:?}"); // [4, 16, 36]
// Works on an infinite source because take limits the pull
let first_three: Vec<u64> = (0..)
.map(|n| n * n)
.take(3)
.collect();
println!("{first_three:?}"); // [0, 1, 4]Follow-up Questions
- What is the difference between iter, iter_mut, and into_iter?
- How does collect know which collection type to build?
- Why can Rust iterators represent infinite sequences safely?
- How do iterator adapters achieve zero-cost abstraction?
- When would you implement the Iterator trait manually?
MCQ Practice
1. When does a call to map on an iterator perform its work?
map is a lazy adapter; the mapping runs only when a consumer such as collect or a for loop drives next.
2. Which method returns Option and defines the Iterator trait?
The Iterator trait requires next, which returns Some(item) until the sequence ends, then None.
3. Why can (0..).map(|n| n).take(3) work on an infinite range?
Because evaluation is lazy, only the demanded elements are computed, so take(3) pulls exactly three items.
Flash Cards
What defines an iterator in Rust? — Implementing the Iterator trait, whose next method yields Option<Item>.
What is a lazy adapter? — A method like map or filter that returns a new iterator and does no work until consumed.
What is a consumer? — A method like collect, sum, or a for loop that drives next and forces evaluation.
Why are iterators zero-cost? — Chained adapters fuse into a single pass that compiles to loop-equivalent machine code.
Can iterators be infinite? — Yes; laziness means only demanded elements are computed, as with (0..).