.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().