· 6 min read
JavaScript iterator helpers: lazy map, filter and take
JavaScript iterator helpers bring lazy map, filter, take and reduce to Map, Set and generators. How they work, when to use them, and the gotchas.

JavaScript iterator helpers are built-in methods like map, filter, take and toArray that work directly on iterators instead of arrays. They are lazy: each value flows through the whole chain one at a time, and nothing runs until you ask for a result. They have been Baseline Newly available since March 2025 and work in Node.js 22 and later, so you can use them today without a library.
If you have ever written [...map.values()].filter(...) just to get access to array methods, this is the feature that removes that step.
What are iterator helpers in JavaScript?
An iterator is any object with a next() method that returns { value, done }. Generators produce them, and so do Map.prototype.keys(), Set.prototype.values(), Array.prototype.values() and string iteration. Until recently, iterators had almost no methods of their own. You could loop over them with for...of, or spread them into an array, and that was it.
Iterator helpers add methods to Iterator.prototype, the shared prototype behind all built-in iterators. There are eleven instance methods and one static method:
| Method | Returns | What it does |
|---|---|---|
map(fn) | iterator | transforms each value |
filter(fn) | iterator | keeps values where fn is truthy |
take(n) | iterator | stops after n values |
drop(n) | iterator | skips the first n values |
flatMap(fn) | iterator | maps, then flattens one level |
reduce(fn, init) | value | folds into a single result |
toArray() | array | collects everything |
forEach(fn) | undefined | runs fn for each value |
some(fn) / every(fn) | boolean | short-circuit tests |
find(fn) | value | first match, or undefined |
Iterator.from(x) | iterator | wraps an iterable or iterator-like object |
The first five return a new iterator, so they chain. The rest consume the iterator and give you a final answer.
Browser support landed in Chrome and Edge 122, Firefox 131 and Safari 18.4. Safari was the last of the three engines, which is what made the feature Baseline at the end of March 2025.
How is lazy evaluation different from array methods?
Array methods are eager: each step builds a complete new array before the next step starts. Iterator helpers are lazy: each value is pulled through the whole chain on demand, and the chain stops as soon as the consumer has what it needs.
Here is the same pipeline both ways, over an array of a million numbers:
const big = Array.from({ length: 1_000_000 }, (_, i) => i);
let lazyCalls = 0;
big.values()
.map(x => { lazyCalls++; return x * 2; })
.filter(x => x % 3 === 0)
.take(3)
.toArray(); // [0, 6, 12]
console.log(lazyCalls); // 7
let eagerCalls = 0;
big
.map(x => { eagerCalls++; return x * 2; })
.filter(x => x % 3 === 0)
.slice(0, 3);
console.log(eagerCalls); // 1000000
The array version maps every element, allocates a million-element array, filters it into another large array, then throws almost all of it away. The iterator version calls the mapper seven times and stops. Same result, very different amount of work.
Laziness also means you can work with sequences that never end. This is impossible with arrays:
function* naturals() {
let n = 1;
while (true) yield n++;
}
naturals()
.filter(n => n % 3 === 0)
.map(n => n * n)
.take(4)
.toArray(); // [9, 36, 81, 144]
Without take, toArray() here would loop forever, so the order of operations matters. Put the limit before anything that consumes the whole sequence.
How do you use iterator helpers with Map and Set?
This is where most code benefits right away. Map and Set hand you iterators, and before this feature you had to convert them to arrays first:
const prices = new Map([["tea", 3], ["coffee", 4], ["cake", 6]]);
// Before
const total = [...prices.values()].reduce((a, b) => a + b, 0);
// Now
const total2 = prices.values().reduce((a, b) => a + b, 0); // 13
const drinks = prices.keys().filter(k => k !== "cake").toArray();
// ["tea", "coffee"]
No intermediate array, and the intent reads straight off the line. If you are building maps of grouped values, this pairs well with Map getOrInsert and getOrInsertComputed, which handles the insert side of the same kind of code.
Helper results are still iterators, so they work anywhere an iterable does: for...of, spread, Array.from, and destructuring.
Iterator.from() covers the remaining cases. It takes an iterable, or a plain object that only has a next() method, and returns a proper iterator with all the helpers attached. That is useful when a library hands you a hand-rolled iterator that does not inherit from Iterator.prototype.
What are the gotchas with iterator helpers?
A few behaviors catch people coming from arrays.
Iterators are single-use. Once consumed, an iterator is done. Calling toArray() twice gives you the values the first time and an empty array the second. If you need to traverse twice, call toArray() once and keep the array, or create a fresh iterator from the source.
const it = [1, 2, 3].values().map(x => x);
it.toArray(); // [1, 2, 3]
it.toArray(); // []
Nothing runs until you consume. Building a chain with map and filter does not call your callbacks. If you rely on side effects inside map, they will not happen until something pulls values, such as next(), toArray() or a loop.
flatMap does not flatten strings. The callback must return an iterator or an iterable object. Returning a plain string throws a TypeError, unlike Array.prototype.flatMap, which would treat the string as a single value. Return [str] or split it explicitly.
Early exit closes the source. When take, find or some stop early, the underlying iterator is closed by calling its return() method. For a generator, that means its finally block runs. This is usually what you want, because it lets generators release resources, but it also means you cannot resume the original generator afterwards.
No index argument. Array callbacks receive (value, index, array). Iterator helper callbacks receive (value, counter), where the counter is a running position, and there is no source collection to pass.
Sync only. These helpers exist on synchronous iterators. Async generators do not get map or filter from this feature, so streams and async sources still need a loop or a library.
When should you use iterator helpers instead of arrays?
Reach for iterator helpers when:
- the source is already an iterator, such as a
Map,Setor generator; - the data is large and you only need part of it;
- the sequence is infinite or expensive to produce;
- you want to avoid allocating intermediate arrays in a hot path.
Stick with arrays when you need random access, sorting, length, multiple passes over the same data, or methods like findLast and includes that iterators do not have. For small arrays the difference is negligible, and the array version is often clearer to readers who have not met iterator helpers yet.
Key takeaways
- Iterator helpers add
map,filter,take,drop,flatMap,reduce,toArrayand more to every built-in iterator. - They are lazy and pull one value at a time, so chains can stop early and handle infinite sequences.
Map,Setand generators no longer need to be spread into arrays first.- Iterators are single-use, and early exit closes the source.
- Supported in all major browsers since March 2025 and in Node.js 22+.
FAQ
Do iterator helpers work in Node.js?
Yes. Node.js 22 and later include them, with no flag needed. You can call .map() or .filter() on any built-in iterator there.
Are iterator helpers faster than array methods?
They do less work when you only need part of the data, because they stop early and skip intermediate arrays. When you consume everything, the difference is small, so choose based on clarity.
Can I use iterator helpers on async generators?
Not with this feature. It covers synchronous iterators only, so async sources still need for await...of or a helper library.