Iterator helpers
Process iterators lazily with the built-in map, filter, take, drop, flatMap and reduce methods, wrap iterables with Iterator.from, and join sequences with Iterator.concat.
What you will learn
- Chain map, filter, take and drop on an iterator and finish with toArray
- Explain how lazy helpers differ from array methods in order of execution
- Wrap iterables and iterator-like objects with Iterator.from
- Use flatMap, reduce, some, every, find and forEach on iterators
On this page
In the generators lesson you wrote filter, map and take as generator functions to process an infinite sequence. You will rarely need to write them again, because modern JavaScript has them built in. Iterator helpers are methods on every iterator that let you chain map, filter and friends the way you do with arrays, but one item at a time and without building the intermediate arrays. They are part of ES2025 and run on Node 22 and newer.
The helpers on a generator
Every generator object, and every iterator from a built-in such as array.values(), map.entries() or set.values(), inherits from a global called Iterator, which carries the helper methods.
function* naturals() { let n = 1; while (true) { yield n++; }}
const firstSquares = naturals() .filter((n) => n % 2 === 0) .map((n) => n * n) .take(4) .toArray();console.log(firstSquares);[ 4, 16, 36, 64 ]console.log([10, 20, 30].values().map((n) => n + 1).toArray());console.log(new Set([1, 2, 3]).values().drop(1).toArray());[ 11, 21, 31 ][ 2, 3 ]The chain reads like an array pipeline: keep the even numbers, square them, take four, and collect. Nothing in it is an array until toArray(). The source naturals() is infinite, which would hang a chain of array methods, but take(4) ends the work after four results.
The last two lines apply the same helpers to the iterators of an array and a set. A plain array does not have these methods, only its values() iterator does, so you call .values() first. Methods that exist on arrays, such as filter and map, are not the same ones. The array versions return arrays, and the iterator versions return iterators.
Lazy, one item at a time
The helpers are lazy. map, filter, take, drop and flatMap return a new iterator and do no work until something pulls values from it. Compare the order of events in an array chain and an iterator chain:
const show = (label) => (n) => { console.log(label, n); return n;};
console.log("array:");[1, 2, 3].map(show("map")).filter(show("filter")).slice(0, 2);array:map 1map 2map 3filter 1filter 2filter 3console.log("iterator:");[1, 2, 3] .values() .map(show("map")) .filter(show("filter")) .take(2) .toArray();iterator:map 1filter 1map 2filter 2The array methods finish a whole stage before the next starts, so all three map calls come before any filter call, and an extra slice is needed to get only two results. The iterator chain pulls one value at a time all the way through: item 1 goes through map then filter, then item 2. The take(2) ends after the second item has passed, so the third item is never mapped. On a big or infinite source, that is the difference between finishing and not.
The same laziness means the helpers return iterators, so they have one use. Call toArray() to turn the rest of the iterator into an array.
Methods that end the chain
Some helpers consume the iterator and return a plain value, much like the array versions:
const words = ["pear", "fig", "apple", "kiwi"];
console.log(words.values().reduce((total, w) => total + w.length, 0));console.log(words.values().some((w) => w.length > 4));console.log(words.values().every((w) => w.length > 2));console.log(words.values().find((w) => w.startsWith("k")));16truetruekiwiwords.values().forEach((w, index) => { console.log(index, w);});
console.log(words.values().map((w, index) => `${index}:${w}`).toArray());0 pear1 fig2 apple3 kiwi[ '0:pear', '1:fig', '2:apple', '3:kiwi' ]reducefolds the values into one. As with arrays, give it a starting value when the iterator might be empty.some,everyandfindstop as soon as the answer is known.forEachruns a function for each value and returnsundefined.- Callbacks for
map,filter,forEachand the rest receive the value and a counter, not the array index you may be used to: the counter counts from 0 for the values that reach that step.
The full list is map, filter, take, drop, flatMap (which return iterators) and reduce, toArray, forEach, some, every, find (which consume).
Iterator.from
The helpers live on the shared prototype, so objects that follow the protocol but do not inherit from it, such as the hand-written iterables of lesson 8, do not have them. Iterator.from(value) wraps any iterable or iterator-like object into a real iterator:
const evens = { [Symbol.iterator]() { let n = 0; return { next: () => ({ value: (n += 2), done: false }) }; },};
console.log(typeof evens.take);console.log(Iterator.from(evens).take(3).toArray());undefined[ 2, 4, 6 ]const countdown = { n: 3, next() { return this.n > 0 ? { value: this.n--, done: false } : { value: undefined, done: true }; },};console.log(Iterator.from(countdown).map((n) => n * 10).toArray());[ 30, 20, 10 ]evens.take is undefined because the object is a plain one. After Iterator.from, take(3) works. The second example is an iterator with a next() method but no [Symbol.iterator]; Iterator.from accepts it as well. If the value already is an iterator that inherits the helpers, Iterator.from returns it as it is.
Flattening with flatMap
flatMap calls a function that returns an iterable, and yields all the values from each result in turn.
const pages = [[1, 2], [3], [], [4, 5]];
console.log(Iterator.from(pages).flatMap((page) => page).toArray());console.log(pages.values().flatMap((page) => page.map((n) => n * 2)).toArray());[ 1, 2, 3, 4, 5 ][ 2, 4, 6, 8, 10 ]try { Iterator.from(["ab"]).flatMap((word) => word).toArray();} catch (error) { console.log(error.name);}TypeErrorconsole.log( Iterator.from(["ab"]) .flatMap((word) => Iterator.from(word)) .toArray(),);[ 'a', 'b' ]Empty inner arrays are skipped, as with Array.prototype.flatMap. One difference catches people out: the function must return an iterator or an iterable object. A bare string is rejected with a TypeError, even though strings are iterable, so wrap it, as in Iterator.from(word).
Cleanup comes for free
When take has the values it needs, it closes the source iterator by calling its return(), so a generator’s finally block runs, as the previous lesson showed.
function* lines() { try { yield "first"; yield "second"; yield "third"; } finally { console.log("source closed"); }}
const found = lines().take(2).toArray();console.log(found);source closed[ 'first', 'second' ]const all = lines().toArray();console.log(all.length);source closed3source closed appears before the first array is printed, because take(2) stopped early and closed the generator. The second generator runs to its natural end, which also runs finally. Either way the resource is released.
Iterator.concat
Requires Node.js 26 or later Iterator.concat(...iterables) joins several iterables into one iterator, yielding all the values of the first, then the second, and so on. It is part of ES2026 and needs Node 26. On Node 22 Iterator.concat does not exist.
const joined = Iterator.concat([1, 2], new Set([3]), [4].values());console.log(joined.toArray());[ 1, 2, 3, 4 ]function* tail() { yield "x"; yield "y";}
const mixed = Iterator.concat(["w"], tail());console.log(mixed.map((s) => s.toUpperCase()).toArray());[ 'W', 'X', 'Y' ]Anything iterable can be joined, including arrays, sets, iterators and generators, and the result has all the helpers. As with flatMap, each argument must be an object, so do not pass a bare string.
Other helpers have been proposed, such as Iterator.zip for walking sequences side by side and chunks, windows and join on iterators. They are not in the language yet, and neither Node 22 nor Node 26 has them. The zip function you wrote in lesson 8 and the chunked generator of lesson 9 do those jobs today.
When to use an iterator pipeline
Use array methods when the data is small and already in an array, when you need the length or random access, or when you will read the result more than once. Use iterator helpers when:
- the source is infinite or very large and you need only the start,
- you want to stop early once a condition is met,
- a long chain would otherwise create several large temporary arrays.
If you only need one result from an array, find on the array is just as good. The benefit is real when the chain has several steps and a lazy source.
Common pitfalls
- Calling a helper on an array.
[1, 2].map(...)is the array method. For the lazy version, write[1, 2].values().map(...). - Forgetting
toArray(). The chain returns an iterator, andconsole.logshowsObject [Iterator Helper] {}instead of your values. - Reusing a consumed iterator. Each helper returns a one-shot iterator. Build the chain again, with a function if you need it twice.
- Spreading or collecting an infinite iterator before
take. PuttakebeforetoArray, not after. - Passing a string to
flatMaporconcat. They reject bare strings. UseIterator.from("text"). - Expecting the index of the original array in callbacks. The second argument is a running counter for that step, which differs after a
filterordrop. - Using helpers on Node older than 22. They are missing.
Iterator.concatneeds Node 26.
Exercises
Write countFrom(start), an infinite generator, and isPrime(n). Use the helpers on countFrom(2) to get the first ten primes as an array, and print them joined by spaces. Then add them up using reduce on the iterator of the same array and print the sum. The expected output is 2 3 5 7 11 13 17 19 23 29 and then 129.
Show solution
filter keeps the primes of the infinite sequence, and take(10) stops after ten, so the program ends. isPrime tries divisors up to the square root of n. Calling values() on the resulting array lets reduce run as an iterator helper, with 0 as the starting value.
function* countFrom(start) { let n = start; while (true) { yield n++; }}
function isPrime(n) { for (let d = 2; d * d <= n; d++) { if (n % d === 0) { return false; } } return n > 1;}
const primes = countFrom(2).filter(isPrime).take(10).toArray();console.log(primes.join(" "));console.log(primes.values().reduce((sum, p) => sum + p, 0));2 3 5 7 11 13 17 19 23 29129Given the six lines INFO start, ERROR disk full, INFO retry, ERROR timeout, ERROR crash and INFO done, find the messages of the first two ERROR lines, without the ERROR prefix, using a lazy chain on the array’s values(). Count how many lines the filter looked at, by incrementing a counter inside its callback. Print the two messages as an array, then checked N of 6 lines. The expected output is [ 'disk full', 'timeout' ] and then checked 4 of 6 lines.
Show solution
The chain stops as soon as take(2) has two results, which happens at the fourth line, so the last two lines are never examined. An array pipeline would have tested all six lines. slice(6) removes the six characters of ERROR .
const lines = [ "INFO start", "ERROR disk full", "INFO retry", "ERROR timeout", "ERROR crash", "INFO done",];
let checked = 0;const firstTwo = lines .values() .filter((line) => { checked += 1; return line.startsWith("ERROR"); }) .map((line) => line.slice(6)) .take(2) .toArray();Print the result and how many lines the chain examined:
console.log(firstTwo);console.log(`checked ${checked} of ${lines.length} lines`);[ 'disk full', 'timeout' ]checked 4 of 6 linesAn API returns its items in pages: [[1, 2], [3, 4, 5], [6]]. Write a function items() that returns a fresh iterator over all the items, using flatMap on the page array’s values(). Print all items as an array. Then print items 2 to 4 using drop(1) and take(3). On one line print whether any item is above 5, and whether all items are below 6. Print the first item divisible by 4, and then the sum of all items using reduce without a starting value. The expected output is [ 1, 2, 3, 4, 5, 6 ], [ 2, 3, 4 ], true false, 4 and 21.
Show solution
items is a function because an iterator can be used once, so every question builds a new one. drop(1) skips the first item, and take(3) ends after three. reduce without a starting value uses the first item as the start, which is fine when the sequence cannot be empty.
const pages = [[1, 2], [3, 4, 5], [6]];
const items = () => pages.values().flatMap((page) => page);
console.log(items().toArray());console.log(items().drop(1).take(3).toArray());console.log(items().some((n) => n > 5), items().every((n) => n < 6));console.log(items().find((n) => n % 4 === 0));console.log(items().reduce((sum, n) => sum + n));[ 1, 2, 3, 4, 5, 6 ][ 2, 3, 4 ]true false421Summary
- Iterator helpers are methods on iterators:
map,filter,take,drop,flatMapreturn lazy iterators, andreduce,toArray,forEach,some,every,findfinish the chain. They are ES2025 and run on Node 22 and newer. - They process one item at a time through the whole chain, so they can work on infinite sources and stop early. Array methods do one stage at a time on whole arrays.
- Generators and the iterators of arrays, maps and sets have the helpers. Use
array.values()to start a lazy chain, andIterator.fromto wrap other iterables and iterator-like objects. takecloses the source when it is done, sofinallyblocks run.- Requires Node.js 26 or later
Iterator.concatjoins iterables and needs Node 26.zip,chunks,windowsandjoinare not available yet.
Next: Map and Set in depth, going beyond the basics of the beginner lesson.