
ES2025 added map, filter, take and drop to iterators. Learn how lazy pipelines skip intermediate arrays, handle infinite sequences, and where they do not help.
Martin Ferret
October 6, 2026
Chain three array methods and you allocate three arrays. Two of them exist only to be thrown away.
const names = users
.filter(u => u.active) // new array
.map(u => u.name) // another one
.slice(0, 10); // and a third
You wanted ten names. You built a full copy of the active users first, then a full copy of their names, then kept 10 of them.
ES2025 fixes this, and the change to your code is one method call.
Every array can hand you an iterator with values(). In ES2025, iterators have their own map, filter, take and drop.
const names = users.values()
.filter(u => u.active)
.map(u => u.name)
.take(10)
.toArray();
Same reading order, same result. No intermediate array at all.
The difference is that array methods are eager. Each one runs to completion before the next one starts. Iterator helpers are lazy: nothing happens until something asks for a value, and then exactly one value travels through the whole chain.
The clearest way to see it is to make the chain talk.
[1, 2, 3].values()
.map(n => { console.log(n); return n * 2; })
.take(1)
.toArray();
// logs 1, and nothing else
toArray asks take for a value. take asks map. map asks the source. One value goes through, take has what it needs, and the pipeline stops. Elements 2 and 3 are never touched.
With array methods, map would have processed all three before slice even ran.
This is why take is the method that changes how you write code. It is a short circuit for the entire chain.
Laziness means a source no longer has to be finite. A generator that never returns is now something you can filter and map like any other collection.
function* naturals() {
let n = 1;
while (true) yield n++;
}
naturals().filter(n => n % 3 === 0).take(4).toArray(); // [3, 6, 9, 12]
Try that with Array.prototype.filter and the tab freezes. Here it stops after finding the fourth multiple.
Eleven helper methods on Iterator.prototype, plus one static.
Five are lazy and return another iterator, so they chain: map, filter, take, drop, flatMap.
Six consume the iterator and return a final value: toArray, reduce, forEach, some, every, find.
Iterator.from() wraps an iterable or an iterator-like object so you can start chaining on it.
You get these on anything whose prototype chain reaches Iterator.prototype, which covers more than arrays: generators, the iterators from Map and Set, the result of String.prototype.matchAll(), and NodeList.prototype.values().
document.querySelectorAll('li').values()
.map(li => li.textContent)
.take(5)
.toArray();
An iterator is consumed once. An array can be read as many times as you like. A pipeline cannot.
const evens = [1, 2, 3, 4].values().filter(n => n % 2 === 0);
evens.toArray(); // [2, 4]
evens.toArray(); // []
The second call finds an exhausted iterator. If you need the result twice, keep the array.
Lazy is not automatically faster. Laziness saves work you would otherwise have done for nothing. On a short array you keep to the end, the per value machinery can cost more than the allocations it avoids. The wins are real when the data is large, when take or find lets you stop early, or when the source is expensive to produce.
The async versions are not here yet. The helpers that shipped are synchronous only. Async iterator helpers are a separate TC39 proposal still in progress, so a streaming response or an async generator still needs for await...of.
Iterator helpers reached Stage 4 at the October 2024 TC39 plenary and shipped in ES2025. They have been Baseline Newly available since March 2025, with Chrome and Edge 122, Firefox 131 and Safari 18.4 as the floor. On the server, Node.js 22 and later.
When a chain ends in slice, find or an early break, you are asking for a fraction of the work you are doing. Add .values() at the start and .toArray() at the end, and the pipeline stops computing as soon as you stop asking.
And once the source can be infinite, generators stop being a curiosity and start being a tool.
Ready to prove your JavaScript skills?
Get certified in JavaScriptKeep learning JavaScript. Then get certified when you are ready.

AbortController: Cancellation and Cleanup in JavaScript
AbortController does more than cancel a fetch. Learn how one signal cleans up event listeners, times out requests, and combines with AbortSignal.any().
Martin Ferret
Sep 22, 2026

Proxy and Reflect: Metaprogramming in JavaScript
Learn how JavaScript Proxy and Reflect intercept object operations: the 13 traps and why Reflect matters.
Martin Ferret
Sep 8, 2026

Intl.NumberFormat and Intl.RelativeTimeFormat: The Traps
Intl.NumberFormat and Intl.RelativeTimeFormat replace your formatting library. Defaults, traps, and the 58x performance mistake to avoid.
Martin Ferret
Aug 25, 2026