Rust Iterators

šŸ¦€ Rust Iterators

map, filter, fold & friends — Cheat sheet

.map()
"Transform each element 1-to-1"
When: You want to apply a function to every element and get a new iterator of transformed values. The bread and butter of iterators.
let nums = vec![1, 2, 3]; let doubled: Vec<i32> = nums.iter() .map(|x| x * 2) .collect(); // [2, 4, 6]
1 → 1 lazy chainable
map is lazy — nothing happens until you consume it (e.g. with .collect()). This is a key difference from imperative loops.
.filter()
"Keep only elements that pass the test"
When: You need a subset of elements matching a condition. The closure receives &&T (double ref) and returns bool.
let nums = vec![1, 2, 3, 4, 5]; let evens: Vec<&i32> = nums.iter() .filter(|x| *x % 2 == 0) .collect(); // [&2, &4]
n → ≤n lazy predicate
Common gotcha: filter gives &&T, not &T. Use filter(|&&x| ...) or filter(|x| **x ...) to destructure.
.filter_map()
"Filter + transform in one step"
When: Your transform might fail (returns Option). Keeps only Some values, discards None. Cleaner than .map().filter().map().
let strings = vec!["1", "abc", "3"]; let nums: Vec<i32> = strings.iter() .filter_map(|s| s.parse().ok()) .collect(); // [1, 3] — "abc" silently dropped
n → ≤n lazy zero overhead
Prefer filter_map over .map().flatten() when working with Option. It's idiomatic and signals intent clearly.
.flat_map()
"Map each element to many, then flatten"
When: Each input produces zero or more outputs. Like .map().flatten() in one step. Great for expanding nested data.
let words = vec!["hello world", "foo bar"]; let chars: Vec<&str> = words.iter() .flat_map(|s| s.split_whitespace()) .collect(); // ["hello", "world", "foo", "bar"]
1 → n lazy chainable
flat_map is the monadic "bind" in FP terms. If you hear "flatMap" in an interview, this is it.
.enumerate()
"Pair each element with its index"
When: You need the index alongside each value. Returns (usize, T) tuples. Much cleaner than manual counter variables.
let fruits = vec!["apple", "banana", "cherry"]; for (i, fruit) in fruits.iter().enumerate() { println!("{i}: {fruit}"); } // 0: apple, 1: banana, 2: cherry
T → (usize, T) lazy
Idiomatic Rust: use .enumerate() instead of for i in 0..vec.len(). Shows you know iterator style.
.zip()
"Merge two iterators into pairs"
When: You want to walk two collections in lockstep. Stops at the shortest. Returns (A, B) tuples.
let names = vec!["Alice", "Bob"]; let scores = vec![95, 87]; let results: Vec<_> = names.iter() .zip(scores.iter()) .collect(); // [("Alice", 95), ("Bob", 87)]
(A,B) → (A,B) lazy chainable
Combine with .map() to merge-and-transform: .zip(b).map(|(a,b)| a + b) for element-wise ops.
.chain()
"Concatenate two iterators end-to-end"
When: You need to iterate over two collections sequentially as if they were one. Both must yield the same type.
let a = vec![1, 2]; let b = vec![3, 4]; let all: Vec<&i32> = a.iter() .chain(b.iter()) .collect(); // [1, 2, 3, 4]
A + B → AB lazy
Useful to prepend/append elements: std::iter::once(0).chain(nums.iter())
.take() / .skip()
"Slice an iterator by count"
When: You want the first n elements (take) or skip the first n (skip). Also .take_while() / .skip_while() for conditions.
let nums = 0..100; let first5: Vec<i32> = nums.clone() .take(5).collect(); // [0,1,2,3,4] let after5: Vec<i32> = nums .skip(5).take(3).collect(); // [5,6,7]
n → ≤n lazy short-circuit
take is essential for infinite iterators: (0..).take(10) safely gives the first 10 natural numbers.
.fold()
"Reduce everything into one value, with an initial"
When: You need to accumulate all elements into a single result. Takes an initial value + a closure (acc, x) → acc. The most powerful consumer.
let nums = vec![1, 2, 3, 4]; let sum = nums.iter() .fold(0, |acc, x| acc + x); // 10 // Build a string let csv = nums.iter() .fold(String::new(), |acc, x| { if acc.is_empty() { x.to_string() } else { format!("{acc},{x}") } }); // "1,2,3,4"
consumes eager n → 1
fold = reduce in JS/Python. It always takes an initial value, so it returns T directly (never Option).
.reduce()
"Like fold, but the first element IS the initial value"
When: Same as fold, but you don't need a separate initial value. Uses the first element as the accumulator. Returns Option (empty iter → None).
let nums = vec![1, 2, 3, 4]; let sum = nums.iter().copied() .reduce(|a, b| a + b); // Some(10) let max = nums.iter().copied() .reduce(i32::max); // Some(4) let empty: Vec<i32> = vec![]; empty.iter().copied().reduce(|a, b| a + b); // None
consumes eager returns Option
fold vs reduce: fold always succeeds (has init); reduce returns Option because the iter might be empty.
.collect()
"Materialize the iterator into a collection"
When: You want to turn a lazy iterator chain into a concrete type: Vec, HashMap, String, HashSet, Result<Vec>, or any FromIterator implementor.
// Into a Vec let v: Vec<i32> = (0..5).collect(); // Into a HashMap use std::collections::HashMap; let m: HashMap<&str, i32> = vec![("a",1), ("b",2)] .into_iter().collect(); // Collect Results — stops at first Err! let r: Result<Vec<i32>, _> = vec!["1","2"].iter() .map(|s| s.parse::<i32>()).collect();
consumes eager turbofish ::
Collecting into Result<Vec<T>, E> is a killer interview trick — it short-circuits on the first error!
.find() / .any() / .all()
"Search and test with short-circuit"
When: You need to check a condition across elements. All three short-circuit — they stop as soon as the answer is known.
let nums = vec![1, 2, 3, 4, 5]; let first_even = nums.iter() .find(|&&x| x % 2 == 0); // Some(&2) let has_neg = nums.iter().any(|&x| x < 0); // false let all_pos = nums.iter().all(|&x| x > 0); // true
consumes short-circuit predicate
Also know .position() (returns index) and .find_map() (find + transform in one pass).
.for_each()
"Consume and run side-effects"
When: You need to perform an action on every element (logging, sending, mutating external state) without producing a value.
vec!["alice", "bob", "carol"] .iter() .for_each(|name| { println!("Hello, {name}!"); });
consumes eager
A for loop is usually preferred for readability. Use for_each when chaining or with par_iter() (rayon).
.partition()
"Split into two collections by a predicate"
When: You want to separate elements into "pass" and "fail" groups in a single pass. Returns a tuple of two collections.
let nums = vec![1, 2, 3, 4, 5]; let (evens, odds): (Vec<&i32>, Vec<&i32>) = nums.iter() .partition(|&&x| x % 2 == 0); // evens: [&2, &4] // odds: [&1, &3, &5]
consumes eager n → (a, b)
Needs type annotation for both sides. Great for separating Ok/Err or valid/invalid items.
.sum() / .product() / .count()
"Quick numeric aggregations"
When: You need simple math aggregations. Cleaner than fold for basic cases. sum and product require Sum/Product trait impls.
let nums = vec![1, 2, 3, 4]; let total: i32 = nums.iter().sum(); // 10 let factorial: i32 = (1..=5).product(); // 120 let n = nums.iter().count(); // 4
consumes eager built-in
These need a type annotation (turbofish or binding). .sum::<i32>() or let s: i32 = iter.sum().

⚔ Key Interview Concepts

Lazy vs Eager

Adaptors (map, filter, take…) are lazy — they build a pipeline but do nothing until a consumer (collect, fold, for_each…) drives it. This enables zero-allocation chaining.

.iter() vs .into_iter() vs .iter_mut()

.iter() → borrows &T
.iter_mut() → mutable borrow &mut T
.into_iter() → takes ownership T
for x in vec calls into_iter() implicitly.

Turbofish ::<>

When Rust can't infer the target type, use turbofish: .collect::<Vec<_>>() or annotate the binding: let v: Vec<_> = .... Common with collect, sum, product.

Iterator Trait

Implement Iterator by defining one method: fn next(&mut self) -> Option<Item>. Everything else (map, filter, fold…) comes for free from the trait's default methods.