Is Your Functional Pipeline Doing Too Much Work?
Functional programming has made code much easier to read. Instead of describing how to loop over a collection, we describe what we want to do with it.
const result = items
.filter(isValid)
.map(transform)
.find(matches);That’s clean, human-readable code. Filter the valid items, transform them, and find the first match.
There’s just one problem: Every array method is eager.
Composing several of them forces the engine to traverse the collection multiple times, allocate intermediate arrays, and compute work the final operation might never need.
The code looks like a single pipeline. The runtime executes three separate loops.
The hidden cost of composition
Take our example again. It looks like a single pipeline: filter some things out and transform the rest. But filter() has to finish before map() can start, and map() has to finish before find() can start.
That’s three traversals and two intermediate array allocations.
The more operations we compose, the more this matters.
The waste becomes clear when the last operation short-circuits. find() only needs a single matching element. But by the time it executes, the previous operations have already processed the entire dataset.
This is particularly wasteful when transform is expensive or the match is near the start.
Imagine
filter()checked 10.000 items,map()transformed 5.000 of them, just forfind()to grab the 10th item and say: “I found it. We’re done”. You’ve just burned CPU cycles on 4,990 transformations that were instantly discarded.
What if the pipeline was actually lazy?
Nothing in our logic requires evaluating the entire array up front, but that’s exactly what array methods require. Iterator helpers let us take it back.
Get Marc Vilella’s stories in your inbox
Join Medium for free to get updates from this writer.
The same code can be expressed as:
const result = Iterator.from(items)
.filter(isValid)
.map(transform)
.find(matches);The syntax is virtually identical, but the timing is completely different.
The iterator doesn’t process the whole collection when you call filter() or map(). Values are processed when something consumes the iterator, the pipeline advances one item at a time.
When find() gets a match, it stops asking for more values. Everything after that item is never processed. That’s a fundamentally different execution model.
The same benefit applies to short-circuiting operations like .some() or .every(). The computation halts the exact moment the answer is known.
Eager is often fine
Iterators aren’t a silver bullet. Every .next() call, iterator object, and abstraction layer introduces a baseline overhead. Replacing array methods with iterators doesn’t always pay off.
const doubled = items.map(value => value * 2);This is simpler, eager, and exactly expresses what you need: transform every element and give me an array.
The question isn’t really: Are iterators better than arrays?
It’s: Do I need to evaluate the entire pipeline right now?
If the answer is yes, an array is often perfectly appropriate. If the answer is no, an iterator gives you a way to defer that decision.
It’s not composition. It’s eagerness
Functional array methods are great. map(), filter() or reduce() make code dramatically easier to write and understand.
The problem isn’t functional composition. The problem is eager composition.
Array methods force immediate evaluation, turning innocent-looking chains into multiple passes and allocations. And we don’t always need it.
Iterator helpers provide lazy evaluation without losing the expressive, declarative syntax. That’s a pretty good reason to keep iterators in the toolbox.




