JavaScript iterator helpers are eleven methods on Iterator.prototype, so map, filter, take, drop, flatMap, reduce, toArray, forEach, some, every and find now work straight off a Map, a Set, a generator or a matchAll() result. The chain is lazy and builds no intermediate arrays. They reached Baseline on 31 March 2025.
Before that, those methods lived only on Array.prototype. The workaround everyone wrote was a spread.
const stock = new Map([['sku-a', 3], ['sku-b', 0], ['sku-c', 12], ['sku-d', 7]]);
// Two throwaway arrays on the way to one number.
const before = [...stock.values()].filter((n) => n > 0).reduce((a, b) => a + b, 0);
// No arrays at all.
const total = stock.values().filter((n) => n > 0).reduce((a, b) => a + b, 0);
// 22
On a four-entry map that is a style preference. On a 40,000-row export it copies the whole thing twice.
What do JavaScript iterator helpers actually do?
Five of them are lazy. map, filter, take, drop and flatMap each return a new Iterator Helper object that pulls from the one before it, one value at a time. Nothing runs until something asks for a value.
Six are eager and consume the iterator to produce a result: reduce, toArray, forEach, some, every and find. Iterator.from is the static method that gets you into the chain from outside. The V8 team’s write-up has the full list with signatures.
Every built-in iterator inherits from Iterator.prototype, so the helpers are already on things you use daily: map.values(), set.entries(), the return of a generator call, params.entries() on URLSearchParams, headers.entries() on a Headers object, and the iterator from String.prototype.matchAll.
Which runtimes support iterator helpers?
Chrome 122 shipped them in February 2024, Firefox 131 in October 2024, Safari 18.4 in March 2025. That last one is the date that matters: webstatus.dev puts Baseline newly available at 31 March 2025. Server-side, Node.js 22.0.0 and Deno 1.39.
The proposal is finished and landed in ECMAScript 2025 as Sync Iterator helpers.
TypeScript needs telling. The declarations live in lib.esnext.iterator.d.ts, added in TypeScript 5.6, and lib.esnext.d.ts is what references it. Compile with "lib": ["es2023"] and you get this, even though the code runs fine:
error TS2339: Property 'filter' does not exist on type 'MapIterator<number>'.
Set "lib": ["ESNext", "DOM"], or add esnext.iterator to the list you already have. If you are touching tsconfig.json anyway, our walkthrough of the TypeScript 7 upgrade covers the rest of that file.
What does lazy evaluation buy you?
Early exit, without writing the loop by hand. Count the pulls:
let pulled = 0;
function* rows() {
for (let i = 0; i < 1000; i++) {
pulled++;
yield i;
}
}
const out = rows().map((n) => n * 2).filter((n) => n % 3 === 0).take(4).toArray();
console.log(out, pulled); // [ 0, 6, 12, 18 ] 10
Ten values leave the generator. The array version reads the same and does a thousand, plus two arrays.
That also makes infinite sources usable. take(n) on a generator that never ends is fine, where [...gen()] hangs the process.
take also cleans up after itself. The spec closes the underlying iterator once the limit is reached, which resumes the generator with a return completion. A finally block runs:
let closed = false;
function* ids() {
try {
let i = 0;
while (true) yield i++;
} finally {
closed = true;
}
}
ids().take(3).toArray(); // [ 0, 1, 2 ]
console.log(closed); // true
So a generator holding a file handle or a database cursor open still releases it when you read only the first few rows.
How do you use helpers on something that is not an iterator?
Iterator.from takes three kinds of input: an existing Iterator instance, which it hands straight back, any iterable, whose Symbol.iterator it calls, and a plain object with a next() method, which it wraps. That last case is the useful one, because hand-rolled iterators and older library objects predate the prototype.
const log = `GET /a 200
GET /b 500
GET /c 503
GET /d 500`;
const failures = log
.matchAll(/ (\d{3})$/gm)
.map((m) => Number(m[1]))
.filter((code) => code >= 500)
.take(2)
.toArray();
// [ 500, 503 ]
const ticker = {
n: 0,
next() {
return this.n < 5
? { value: this.n++, done: false }
: { value: undefined, done: true };
},
};
Iterator.from(ticker).map((n) => n * 2).toArray(); // [ 0, 2, 4, 6, 8 ]
What trips people up?
A helper is single use. Consume it twice and the second call gets nothing back, silently.
const helper = [1, 2, 3].values().map((n) => n * 2);
helper.toArray(); // [ 2, 4, 6 ]
helper.toArray(); // []
[1, 2].values().take(-1); // RangeError: -1 must be positive
take and drop throw a RangeError when the limit converts to NaN or a negative number, so take(items.length - 5) on a short list throws rather than returning nothing.
There is no sort, no reverse, no groupBy. Sorting needs every value in memory, so it cannot be lazy and the proposal left it out. Call toArray() and sort that. For grouping, Object.groupBy already accepts any iterable, including a helper, and has been in Node since 21.0.0.
The callbacks take (value, index) like their array cousins, where the index is a counter over what the helper has seen, not a position in a source array. Helpers have no length and no indexed access, which is the main reason to stop chaining and call toArray().
What is still missing, as of September 2026?
Iterator.concat, for running several iterables end to end, is Iterator Sequencing in ECMAScript 2026. Chrome 146, Firefox 147, Safari 26.4, Node.js 26.0.0 and Deno 2.7.2 have it. Iterator.zip and Iterator.zipKeyed come from Joint Iteration, finished for ECMAScript 2027, and are in Chrome 153 and Firefox 148 but not yet in a Node release. chunks and windows are further back still: Firefox 154 and a Safari preview, nothing in Chrome.
The bigger gap is async. Async iterator helpers, which would add Iterator.prototype.toAsync, AsyncIterator.from and the same method set for async iteration, sit at Stage 2. So a Node stream, an async generator or anything you consume with for await gets none of this today. Those pipelines still need a loop or a library.
Would I use them?
Yes, on anything whose floor is Node.js 22 or Baseline 2025, and by preference over adding a utility dependency for one chain. The clean wins are the ones where the source is already an iterator and the destination is a single value: totals over map.values(), a first match out of matchAll, the first page of results from a generator that reads a file.
I would not go back through working array code to convert it. Arrays sort, index and print in a debugger, helpers do none of those, and a .values() bolted on to reach a lazy chain you then flatten with toArray() is a worse line than the one it replaced. The Temporal API got the same answer here for the same reason: adopt it where it removes work, not as a sweep. See our take on when to move to Temporal.
The one place to be careful is library code. Helpers on a value you return means callers can only iterate it once, and nothing in the type signature warns them. Return an array there, and keep the laziness inside.