completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Iterator helpers

Build lazy JavaScript data pipelines with Iterator.from, map, filter, take, drop, flatMap, consuming helpers, and feature-detected newer iterator proposals.

By the end, you can
  • 01
    Follow a lazy chainTrace how a chain of iterator helpers pulls one value at a time and stops early.
  • 02
    Know which helper ends the chainTell a helper that gives back another iterator from one that pulls the values out and gives a result.
  • 03
    Use the helpers safelyUse Iterator.from, remember that an iterator can be used only once, and check that newer helpers exist before you use them.

Array-style helpers for iterators

An iterator is an object that gives you values one at a time. Each time you call its next() method, you get the next value. Generators give you iterators, and so do methods such as entries() on a Map or a Set.

Iterator helpers are methods you can call on an iterator. Many have the same names as array methods, such as map and filter. If you know map, filter, and reduce on arrays, the names will look familiar.

The big difference is when the work happens. An array method is eager: it does its work right away, on every item. Most iterator helpers are lazy: they do no work until someone asks for the next value. Asking an iterator for its next value is called pulling a value.

These are the standard helpers this lesson covers:

  • Iterator.from gives you an iterator that has the helpers.
  • map, filter, take, drop, and flatMap give back a new iterator. They wait.
  • toArray, reduce, forEach, some, every, and find pull the values out and give back a result.

They work in current browsers and in Node 22. Older browsers may not have them, so code for those browsers should check first.

The main idea

Helpers like map give back another iterator and wait. Helpers like toArray pull the values and give back a final result. This lesson calls them consuming helpers, because they use up the values. Once a value is pulled, the iterator does not go back to it.

Real-life analogyA shopping list you read while you shop

You read one item on your shopping list. You find it and put it in your basket. Only then do you read the next item. If you only need three things, you stop after three. You never copy out the whole list first.

In real life: Read one item on the list
In JavaScript: Pull one value from the source
In real life: Find that item before you read the next one
In JavaScript: That one value goes through map, then filter
In real life: Stop once you have three items
In JavaScript: take(3) stops pulling
In real life: Copy out the whole list before you start
In JavaScript: An array method works on every item first

Iterator.from adds the helpers

ADAPTER

An iterator is any object with a next() method. Each call to next() returns a small object shaped like { value, done }. The value part is the next value. The done part is true when there are no more values.

An object you write by hand with only a next() method does not get the helpers. It has no map and no toArray. The fix is Iterator.from(value). It gives back an iterator that pulls from your object and also has every helper.

Real-life analogyIterator.from is a travel adapter

In another country, your charger plug does not fit the wall socket. A travel adapter fixes the shape. Your charger works the same as before; now it also fits.

In real life: You plug your own charger into a travel adapter
In JavaScript: You pass your object to Iterator.from
In real life: The adapter fits the wall socket
In JavaScript: The result has the helper methods
In real life: The adapter makes no power of its own
In JavaScript: The values still come from your object
Give helpers to an object that only has next()Pop out in the code editor (opens in a new tab)JavaScript
const handRolled = {  current: 0,  next() {    this.current += 1;    return this.current <= 3      ? { value: this.current, done: false }      : { value: undefined, done: true };  },}; const result = Iterator.from(handRolled)  .map(n => n * 10)  .toArray(); console.log(result);

It prints [10, 20, 30]. Line by line:

  1. Lines 1–9 make handRolled. It gives 1, 2, 3, then says done: true. It has only next().
  2. Line 11 passes it to Iterator.from, which gives back an iterator with the helpers.
  3. Line 12 multiplies each value by 10.
  4. Line 13 calls toArray(), which pulls every value and puts them in an array.
A box called handRolled holds only a next() method. An arrow labelled Iterator.from leads to an iterator that has map() and toArray().next()handRolledonly next()Iterator.frommap()toArray()iteratornow has helpers
handRolled can only give values with next(). Iterator.from wraps it in an iterator that also has map() and toArray().

You can do the same with any object that gives values through next(), such as a reader you wrote for your own data.

Format Map entries one at a timePop out in the code editor (opens in a new tab)JavaScript
const prices = new Map([  ["tea", 3],  ["bread", 4],  ["rice", 8],]); const labels = Iterator.from(prices.entries())  .map(([name, price]) => name + ":" + price)  .toArray(); console.log(labels.join(", "));

prices.entries() already returns an iterator. Each value it gives is a [name, price] pair. Built-in iterators like this one already have the helpers, so Iterator.from just hands the same iterator back.

  1. Line 8 turns one pair at a time into text like "tea:3".
  2. Line 9 calls toArray() to collect the results.
  3. Line 11 prints tea:3, bread:4, rice:8.

No extra array of pairs is made along the way.

Iterator or array?

Use an iterator when the values come one at a time. Use an array when you need to:

  • jump to an item by its position, like list[2],
  • go through the values more than once, or
  • have all the values right now.

A lazy chain handles one value at a time

INTERACTIVE

A generator is a function that makes values one at a time, only when asked. Calling a generator gives you an iterator, so you can use the helpers on it straight away. The generator below counts up forever and gives out only the even numbers.

Take three values from a generator that never endsPop out in the code editor (opens in a new tab)JavaScript
function* evenNumbers() {  let value = 0;  while (true) {    if (value % 2 === 0) yield value;    value += 1;  }} const firstThree = evenNumbers().take(3).toArray();console.log(firstThree);

Line 9 asks for only three values, so it gets [0, 2, 4]. After the third value, take(3) stops pulling, and the endless loop is never asked again. Without take(3), toArray() would wait forever, because this generator never says done: true.

The tool below runs a longer chain. A function you pass to a helper, like n => n * 2, is called a callback. Each part of the chain logs a line when it runs:

  • pull means the source gave one value.
  • map means the map callback ran on that value.
  • filter means the filter callback checked the result.
Pipeline visualizer: one value at a time, or all at once?
Iterator chainPop out in the code editor (opens in a new tab)JavaScript
function* numbers() {  for (const value of [1, 2, 3, 4, 5, 6]) {    console.log("pull " + value);    yield value;  }} const result = numbers()  .map(n => { console.log("map " + n); return n * 2; })  .filter(n => { console.log("filter " + n); return n % 3 !== 0; })  .take(3)  .toArray(); console.log(result.join(", "));
Live pull log2, 4, 8
  1. pull 1
  2. map 1
  3. filter 2
  4. pull 2
  5. map 2
  6. filter 4
  7. pull 3
  8. map 3
  9. filter 6
  10. pull 4
  11. map 4
  12. filter 8
  13. result 2, 4, 8
pulls 4map 4filter 4
Try it yourself
Run as:

The iterator chain pulled 4 source values, made 8 callback calls, and stopped as soon as take(3) had enough.

Both versions give the same values. The log shows the order of the work and how much work was done.

Switch to the array version. The result is still 2, 4, 8, but the log changes:

  • The array chain runs map on all six numbers. Only then does filter start.
  • The iterator chain pulls one value, maps it, and filters it. Then it pulls the next one.
  • The iterator chain stops after the fourth value, because three values have passed the filter by then.
Left, the iterator chain: value 1 goes through map then filter, then value 2, then 3, then 4. Values 5 and 6 are never pulled, so there are 8 callback calls. Right, the array chain: map runs on all 6 values, then filter on all 6, then slice. That is 12 callback calls.iterator chain1 → map → filter2 → map → filter3 → map → filter4 → map → filter5 and 6 never pulled8 callback callsarray chainmap all 6filter all 6slice(0, 3)every value, every step12 callback calls
Both chains give 2, 4, 8. The iterator chain takes one value through every step, then stops after 4 values. The array chain does each step for all 6 values.
Step through the iterator chain and the array chain
Step 0 of 18Ready
Your turn: follow the blue line

Pick lazy or array, then step. Watch one value go through every step before the next value starts.

Running in
  1. script
Next: line 8
function* numbers() {  for (const value of [1, 2, 3, 4, 5, 6]) {    console.log("pull " + value);    yield value;  }}   .map(n => { console.log("map " + n); return n * 2; })  .filter(n => { console.log("filter " + n); return n % 3 !== 0; })  .take(3)  .toArray(); console.log(result.join(", "));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Pick which chain to step through

Changing this starts a new run, with a different order and number of calls.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Which browsers have these helpers

The standard helpers in this lesson work in current Chrome, Firefox, Safari, and Node 22. Some people still use older browsers. If your code must run there too, check that a helper exists before you use it.

map, filter, take, drop and flatMap

These five helpers each give back a new iterator. Nothing happens until something pulls from it. The table also lists the consuming helpers from the next section, so you can compare them. Read each row as: the helper, whether it waits to be asked, what it gives back, and what it is for.

What each helper does
HelperDoes it wait to be asked?What it gives backWhat it is for
map(callback)Yes. It runs only when a value is pulled.A new iterator.Change each value.
filter(callback)Yes. It runs only when a value is pulled.A new iterator.Keep values where the callback's answer counts as true.
take(n) / drop(n)Yes. It runs only when a value is pulled.A new iterator.Keep or skip the first n values. n can't be negative.
flatMap(callback)Yes. It runs only when a value is pulled.A new iterator.Turn each value into zero or more values.
toArray()No. It pulls every value.An array.Collect the remaining values.
reduce(callback, initial)No. It pulls every value.One value.Combine all remaining values into one.
some / every / findNo. It pulls until it knows the answer.true or false, or the value it found.Search, and stop early.
forEach(callback)No. It pulls every value.undefined.Do something with each value, like logging it.
  • map(callback) changes each value it pulls.
  • filter(callback) keeps a value when the callback returns a truthy value, which is a value that counts as true.
  • take(n) lets the first n values through, then stops.
  • drop(n) skips the first n values and lets the rest through.

take and drop check their number. A negative number or NaN throws a RangeError, which is an error for a number outside the allowed range. They do not quietly guess what you meant.

flatMap is for when one value should become several values, or none. Its callback returns something you can loop over, like an array. The helper then gives out each item inside it, one by one.

flatMap turns each basket into its itemsPop out in the code editor (opens in a new tab)JavaScript
const baskets = [["tea", "bread"], ["rice"]];const items = Iterator.from(baskets)  .flatMap(basket => basket)  .toArray(); console.log(items.join(", "));

Each basket is an array, so flatMap can loop over it. It gives out tea, then bread, then rice, and line 6 prints tea, bread, rice. If the callback returned a number instead, flatMap would throw a TypeError, because you can't loop over a number.

You can use flatMap to split lines into words, to turn one calendar day into its events, or to turn one page of results into single rows.

An iterator remembers where it is. Once it gives out a value, it never gives that value again. An iterator that can be used only once like this is called one-shot.

One iterator, used twicePop out in the code editor (opens in a new tab)JavaScript
const iterator = Iterator.from([1, 2, 3]);console.log(iterator.take(2).toArray());console.log(iterator.toArray());

Line 2 prints [1, 2]. Line 3 prints [3], because line 2 already pulled 1 and 2 from the same iterator. To go through the values again, keep the original array, or make a fresh iterator.

One iterator over 1, 2, 3. On line 2, take(2) pulls 1 and 2 and gives [1, 2]. On line 3, toArray() gets only what is left, [3].Iterator.from([1, 2, 3])123take(2)[1, 2]line 2toArray()[3]line 3
Line 2 uses up 1 and 2. Line 3 asks the same iterator again, so it starts at 3.

Some sources keep something open, such as a file or a database connection. They can have a return() method that cleans up. When a helper stops early, it calls that method on the source.

take tells the source it can clean upPop out in the code editor (opens in a new tab)JavaScript
function makeReader(log) {  let value = 1;  return {    next() {      return { value: value++, done: false };    },    return() {      log.push("closed");      return { value: undefined, done: true };    },    [Symbol.iterator]() { return this; },  };} const log = [];const values = Iterator.from(makeReader(log)).take(2).toArray();console.log(values);console.log(log);

The reader on lines 1–13 counts 1, 2, 3, and so on forever. Its return() method on lines 7–10 adds "closed" to the log. When take(2) has its two values, it calls return() before the chain finishes. So line 17 prints [1, 2] and line 18 prints ["closed"]. A real source can close its file or connection at that moment.

some, every, find, reduce, forEach and toArray

Consuming helpers pull values out of the chain. They give back a final result, not another iterator.

  • toArray() puts every remaining value into an array.
  • reduce(callback, initial) combines every remaining value into one value, starting from initial.
  • forEach(callback) calls the callback for each value, for example to log it. It returns undefined.
  • some, every, and find stop as soon as they know the answer.
find stops at the first matchPop out in the code editor (opens in a new tab)JavaScript
let checks = 0;const firstOdd = Iterator.from([2, 4, 7, 8]).find(value => {  checks += 1;  return value % 2 === 1;}); console.log(firstOdd + ":" + checks);

find checks 2, then 4, then 7. 7 is odd, so it stops there. Line 7 prints 7:3: the value found, then the number of checks. The callback never sees 8.

find checks 2, then 4, then 7. 7 is odd, so find stops after 3 checks. 8 is never checked. The program prints 7:3.find(value => value % 2 === 1)check 1check 2check 3never checked24787:3printed
find checks one value at a time. It stops at 7, the first odd value, so it prints 7:3.

Stopping early helps most with long or endless sources:

  • find stops at the first value that matches.
  • some stops at the first value where the callback returns a truthy value.
  • every stops at the first value where the callback returns a falsy value, one that counts as false.

On an endless source, use these only when the answer will come at some point. Otherwise they keep checking forever.

A chain with no consuming helper does nothing

Iterator.from([1, 2, 3]).map(n => n * 2) only describes the work. It prints nothing and makes no array. To get values, end the chain with toArray(), find(...), or reduce(...), or loop over it with for...of.

Helper playground

INTERACTIVE

Pick a source, turn helpers on or off, then pick a consuming helper. The infinite counter counts 1, 2, 3, and so on forever. The playground refuses any chain that would pull from it forever. Real code needs the same care with endless sources.

Helper playground: build a safe chain
The chain the playground buildsPop out in the code editor (opens in a new tab)JavaScript
function* counter() {  let value = 1;  while (true) yield value++;} const result = Iterator.from(source)  .drop(1)  .map(n => n * 2)  .filter(n => n % 3 !== 0)  .take(6)  .toArray();
Resultsafe
  • chainarray → map(n => n * 2) → filter(n => n % 3 !== 0) → take(6) → toArray()
    result[2, 4, 8, 10]
Try it yourself
Source:Helpers:

Result: [2, 4, 8, 10]. Chain: array → map(n => n * 2) → filter(n => n % 3 !== 0) → take(6) → toArray().

The playground runs callbacks that are written in advance. An endless counter needs take, find, or some to stop it.

An array, a Set, a generator, and the endless counter all use the same helpers. Each of them gives an iterator, and the helpers work on any iterator.

Newer helpers: concat, zip, and proposals

FEATURE DETECT

New JavaScript features are planned by a group called TC39. Each planned feature is called a proposal. A proposal moves through numbered stages, and stage 4 means finished. At the time of writing, TC39 lists these as finished:

  • Iterator helpers: the standard helpers in this lesson.
  • Joint Iteration: Iterator.zip and Iterator.zipKeyed, which pair up values from several iterators by position.
  • Iterator Sequencing: Iterator.concat, which joins several iterators one after another.

Finished does not mean every browser or Node version has it yet. Node 22 has the standard helpers, but not Iterator.concat, Iterator.zip, or Iterator.zipKeyed. So check that a newer helper exists before you use it. This check is called feature detection.

Check for newer helpers before using themPop out in the code editor (opens in a new tab)JavaScript
// At the time of writing, Node 22 does not expose these yet.// Feature-detect before using them in production code.const canConcat = typeof Iterator.concat === "function";const canZip = typeof Iterator.zip === "function";const canZipKeyed = typeof Iterator.zipKeyed === "function"; console.log({ canConcat, canZip, canZipKeyed });

Lines 3–5 use typeof, which gives the type of a value as text. A method that exists gives "function". In Node 22, all three checks are false.

TC39 also lists three ideas at stage 3, one step before finished:

  • Iterator chunking: splitting values into groups, called chunks, or into sliding windows.
  • Iterator Includes: checking whether an iterator gives a certain value.
  • Iterator Join: joining the values into one string.

Until the places your code runs have these, write a small generator yourself or use a library. Make it clear that it is your own code, not standard JavaScript.

Your own chunks generator, not a built-inPop out in the code editor (opens in a new tab)JavaScript
function* chunks(iterable, size) {  let chunk = [];  for (const value of iterable) {    chunk.push(value);    if (chunk.length === size) {      yield chunk;      chunk = [];    }  }  if (chunk.length) yield chunk;}
  1. Line 1 starts a generator that takes anything you can loop over, plus a group size.
  2. Lines 3–4 add each value to the current group, called chunk.
  3. When the group reaches size items, line 6 gives it out and line 7 starts a new one.
  4. Line 10 gives out the last, shorter group, if there is one.
Check which helpers this browser has
Check for newer helpersPop out in the code editor (opens in a new tab)JavaScript
// At the time of writing, Node 22 does not expose these yet.// Feature-detect before using them in production code.const canConcat = typeof Iterator.concat === "function";const canZip = typeof Iterator.zip === "function";const canZipKeyed = typeof Iterator.zipKeyed === "function"; console.log({ canConcat, canZip, canZipKeyed });
Your own chunks generator, for when there is no built-inPop out in the code editor (opens in a new tab)JavaScript
function* chunks(iterable, size) {  let chunk = [];  for (const value of iterable) {    chunk.push(value);    if (chunk.length === size) {      yield chunk;      chunk = [];    }  }  if (chunk.length) yield chunk;}
Support in this browserwaiting

Standard iterator helpers

  • from: not found
  • map: not found
  • filter: not found
  • take: not found
  • drop: not found
  • flatMap: not found
  • reduce: not found
  • toArray: not found
  • forEach: not found
  • some: not found
  • every: not found
  • find: not found

Newer and proposed helpers

  • concat: not found
  • zip: not found
  • zipKeyed: not found
  • chunks: not found
  • windows: not found
  • includes: not found
  • join: not found
Try it yourself

The check runs once the page has loaded in your browser.

This panel checks your own browser after the page loads, not the server that sent the page.

Where you'll use this

Iterator helpers help when the source is not a small array, or when stopping early saves work. For example:

  • reading lines from a big log file,
  • making IDs one at a time,
  • loading results one page at a time,
  • reading rows from a local database.

They also keep a chain easy to read without making a new array at every step.

Lazy or eager?
  • Find the first error line in a huge log
  • Show the next 20 numbers from an endless counter
  • Add up every invoice in a finished monthly list
  • Show every search result that is already loaded
  • Skip the first 100 sensor readings, then show 10
  • Sort products by price before showing page one
Try it yourself
0 of 6 correct

Put each task under the style that fits it best.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Eager is not bad. If you already have a small array and need every value, array methods are often clearer. Use lazy helpers when you want to stop early, save memory, or work with an endless source.

Common misconceptions

  • “map runs right away.” Iterator map gives back a new iterator and waits. A consuming helper pulls the values.
  • “Iterators go back to the start, like arrays.” Most iterators are one-shot. A value that was pulled is gone.
  • “take(-1) means take none.” A negative count throws a RangeError.
  • “toArray is always safe.” On an endless source, it never finishes. Add take first, or use find or some.
  • “A finished proposal works everywhere today.” Some places don't have it yet. Check for newer names such as Iterator.zip first.
  • “Lazy is always faster.” Sorting, grouping, and adding up every value still need all the values.

The table compares the two styles. Read each row as a question, then the answer for iterator helpers and for array methods.

Iterator helpers versus array methods
QuestionIterator helpersArray methods
When do the callbacks run?When a consuming helper pulls a value.Right away, while the method runs.
Can the source be endless?Yes, if a later helper stops it.No. An array always has a fixed number of items.
Can you use the result twice?Usually not. It is one-shot.Yes. An array keeps its items.
How do you get an array?Call toArray().You already have one.

Practice exercises

4 EXERCISES
Exercise 1 · Warm-upPredict a lazy result

Run the program in your head. Type the exact text that line 12 prints.

Starter codePop out in the code editor (opens in a new tab)JavaScript
function* ids() {
  yield 1;
  yield 2;
  yield 3;
}

const result = ids()
  .map(id => id * 10)
  .take(2)
  .toArray();

console.log(result.join("|"));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeSpot the one-shot iterator

    What does the second console.log print?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const values = Iterator.from(["a", "b", "c"]);
    console.log(values.take(2).toArray().join(""));
    console.log(values.toArray().join(""));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFind the bug: no consuming helper

      The code prints an iterator object, not a number. Fix it so it prints the first even value above 6.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const firstLarge = Iterator.from([2, 5, 8, 11])
        .filter(n => n > 6);
      console.log(firstLarge);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeAdd up after map

        Predict the total that this chain prints.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const total = Iterator.from([3, 4, 5])
          .map(n => n * 2)
          .reduce((sum, n) => sum + n, 0);
        console.log(total);

        Answer, then press Check. Spacing and letter case don’t matter.

          Check your understanding

          7 QUESTIONS
          Iterator helpers quiz · 7 questionsScore: first tries count
          1. Question 1 of 7What does it mean that iterator helpers are lazy?

            Choose an answer to see the explanation.

          2. Question 2 of 7This code uses one iterator twice. What does the second log print?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            const it = Iterator.from([1, 2, 3]);
            console.log(it.take(2).toArray().join(","));
            console.log(it.toArray().join(","));

            Choose an answer to see the explanation.

          3. Question 3 of 7What does take do with a negative count?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            try {
              Iterator.from([1]).take(-1).next();
            } catch (error) {
              console.log(error.name);
            }

            Choose an answer to see the explanation.

          4. Question 4 of 7Which helper pulls the values out and gives back an array?

            Choose an answer to see the explanation.

          5. Question 5 of 7What does this search print?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            let calls = 0;
            const found = Iterator.from([2, 4, 6, 9, 10]).find(n => {
              calls += 1;
              return n % 2 === 1;
            });
            console.log(found + ":" + calls);

            Choose an answer to see the explanation.

          6. Question 6 of 7At the time of writing, how should you use Iterator.zip and Iterator.concat?

            Choose an answer to see the explanation.

          7. Question 7 of 7Which task fits a lazy iterator chain best?

            Choose an answer to see the explanation.

          Key takeaways

          • Iterator.from gives the helpers to an object that only has next().
          • map, filter, take, drop, and flatMap are lazy. They give back a new iterator and wait.
          • toArray, reduce, forEach, some, every, and find pull the remaining values and give back a result.
          • An iterator does not go back to the start. Make a fresh one if you need the values again.
          • Check that newer helpers, such as Iterator.zip, exist before you use them.

          Iterator helpers are methods for iterators. Most of them wait, and work on one value at a time, only when a value is pulled.

          Up next: Async iteration & async generators.

          CompleteFrontend Clear concepts. Working examples.