Under The Hood
backend rust Rust 1.x

Rust Iterators: Zero-Cost Functional Pipelines vs Handwritten Loops

Last updated
Prerequisites:
Rust basics (collections, traits)
Understanding of compiler optimization and inlining
  • rust
  • iterators
  • zero-cost
  • optimization
  • functional
  • inlining

Read at your depth

The practical view

Rust iterators let you write functional pipelines: iter().filter(...).map(...).take(10).collect(). These are lazy: nothing runs until collected/consumed. The claim 'zero-cost' means the compiled output should be as fast as a handwritten loop — because the iterator adapters are generic and inline into each other. For example: nums.iter().filter(|n| n % 2 == 0).map(|n| n * 2).sum::<i32>() should optimize to a single loop with no iterator object overhead. Use iterators for clarity; they're designed to compile down to the same machine code as manual loops.

The same idea in other frameworks

react equivalent
N/A — Rust language feature.

Legacy vs modern

Imperative loop with intermediate allocations vs iterator pipeline

An imperative version often allocates intermediate collections and repeats passes; the iterator pipeline fuses into one pass with no intermediate storage.

before → after
Multi-pass imperative style
let mut evens = Vec::new(); // ❌ OLD: intermediate allocation
Zero-cost iterator pipeline
nums.iter().filter(|n| n % 2 == 0).map(|n| n * 2).sum() // ✅ NEW

Interview gotchas

Context

This tests the optimization model — interviewers want the monomorphization+inlining mechanism AND the honest cases where the promise can break.

The mechanical answer

Iterators are zero-cost when the chain is monomorphized and inlined: each adapter is a generic struct whose next() calls the inner next(); with concrete types, the compiler inlines the whole chain into a single loop, eliminating the iterator object and enabling loop optimizations (bounds-check elimination via size_hint, vectorization). The promise can break when: the chain is used across a function boundary without inlining (e.g. a boxed dyn Iterator, which forces dynamic dispatch and prevents fusion); closures that capture large state inhibit inlining; or when the iterator produces values that cannot be fused (e.g. collect() into a collection necessarily allocates). Also, if you call an adapter that needs the full collection (like .rev() on a double-ended iterator that requires RandomAccess), the fusion may differ. In general, for linear single-pass pipelines, iterators match hand loops.

Trap

The naive answer is 'iterators are always zero-cost by definition'. Zero-cost depends on inlining and concrete types; Box<dyn Iterator> and cross-crate non-inlined paths pay real costs. Another trap: assuming every collect() is free — collecting into a Vec allocates, exactly like building the Vec by hand. Candidates who explain the inlining dependency show real understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.