Course contentsShow
← Rust
Lesson 967 of 1,9108. Collections and Iterator PipelinesFree lesson

Strict vs Lazy Collection Trade-offs

You will be able to identify scenarios where eager collection evaluation outperforms lazy streaming.

In this lesson, you will learn how to identify specific scenarios where the immediate, strict evaluation of collections outperforms the deferred execution of lazy iterator pipelines.

The Cost of Laziness

We often praise Rust’s iterators for being "lazy" and efficient—they avoid unnecessary allocations and only compute values as requested. However, laziness isn't free. Every iterator step carries a small overhead: it must track its state and perform a branch to check if the next element exists. When performing complex, multi-pass operations on data that already resides in memory, these small overheads accumulate.

When Strictness Wins

Eager (strict) collection evaluation—using methods like .collect::<Vec<_>>()—becomes the better choice in three primary scenarios:

  1. Intermediate Materialization: If you need to traverse the same dataset multiple times, keeping it in a strict Vec is faster than re-running a complex lazy pipeline, which would re-compute values on every pass.
  2. Memory Locality: Strict collections store elements contiguously in a single block of memory. Iterating over a Vec allows the CPU to leverage prefetching and cache lines effectively. A lazy chain of complex adapters may cause "pointer chasing" or complex branching that stalls the CPU pipeline.
  3. SIMD and Optimization Hints: Compilers have a much easier time applying auto-vectorization (SIMD) to a simple loop over a slice of data than to a chain of abstract iterator methods.

Illustration

Consider a scenario where you filter a massive list and then perform multiple distinct lookups on that filtered result:

// Lazy: Re-filters every time you call .find()
let data = (0..1_000_000).filter(|x| x % 2 == 0); 
// Subsequent lookups here are expensive!

// Strict: Compute once, then cache
let filtered: Vec<_> = (0..1_000_000).filter(|x| x % 2 == 0).collect();
// Subsequent operations are just simple memory access

In the strict version, you pay the allocation cost upfront, but you gain cache-friendly sequential access for all subsequent operations. By "materializing" the data, you trade a one-time memory allocation for significantly faster throughput on future processing steps.

Key Takeaway: Choose lazy pipelines for streaming and one-pass transformations, but prefer strict materialization when you need multiple passes, cache locality, or opportunities for compiler-led SIMD vectorization.