cf.completefrontendCode editorOpen lab
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
    Build lazy pipelinesTrace how iterator helpers pull one value at a time and stop early.
  • 02
    Choose a terminal helperKnow when a chain returns another iterator and when it consumes the source.
  • 03
    Ship defensivelyUse Iterator.from, understand one-shot consumption, and feature-detect newer helpers.

Lazy helpers for any iterator

Iterator helpers bring familiar array-method names to values that are produced over time. If you know map, filter, and reduce on arrays, the names will feel friendly. The big difference is timing: iterator helpers are usually lazy. They do not run the whole source immediately. They wait until something asks for the next value.

The core standardized helpers are Iterator.from, map, filter, take, drop, flatMap, reduce, toArray, forEach, some, every, and find. They are supported in modern browsers and Node 22; feature-detect for older environments.

The short version

Transforming helpers return another iterator. Consuming helpers pull values and return a final result. Once values are pulled, a one-shot iterator does not rewind.

Real-life analogyA shopping list you read when you shop

Read one shopping-list item, check it, then move to the next. If you only need three items, stop there. An array chain often prepares a full list before moving to the next step.

In real life: Read one item on the list
In JavaScript: One value reaches map
In real life: Check that same item
In JavaScript: The mapped value reaches filter
In real life: Stop after enough items
In JavaScript: take(3) stops pulling the source
In real life: Write every item before shopping
In JavaScript: Array methods usually build whole intermediate arrays

Where the analogy stops: A paper list does not create items. A JavaScript iterator pauses between pulls; no helper callback runs until the next value is requested.

You will compare lazy and eager chains, adapt a plain next() object, play with every helper, feature-detect newer proposals, sort realistic tasks, then practice with predictions and bugs.

Iterator.from adapts next() objects

ADAPTER

An iterator is any object with a next() method that returns { value, done } records. That protocol is powerful, but a hand-rolled iterator does not automatically have helper methods. Iterator.from(value) is the adapter: it returns an iterator object that can use helper methods.

Real-life analogyIterator.from is an adapter plug

The source already knows how to produce values. It just lacks the new outlet shape. Iterator.from fits that source into the helper-method world.

In real life: A travel adapter accepts the plug you already own
In JavaScript: Iterator.from accepts an iterator-like object
In real life: The wall side now has the right shape
In JavaScript: The returned object has helper methods
In real life: The adapter does not create electricity
In JavaScript: It still pulls values from the original source

Where the analogy stops: An adapter plug can be reused physically. Many iterators are one-shot: after the original source is drained, wrapping it again does not restore spent values.

Adapting a plain next objectPop 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);

The output is [10, 20, 30]. The object on lines 1–9 only has next(). After line 11 adapts it, map and toArray are available. This matters for generators, custom data readers, and protocol-based APIs you will meet in the async iteration lesson.

The lazy conveyor belt

INTERACTIVE

Read the lazy chain first. numbers() logs whenever the source is pulled. The map and filter callbacks log too. With take(3), the chain should stop after three passing values, not after the whole source is exhausted.

Pipeline visualizer: one plate or whole banquet?
Lazy 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 lazy chain pulled 4 source values, ran 8 callbacks, and stopped as soon as take(3) had enough.

Both modes compute the same values. The log shows the order and amount of work.

Switch to the eager array version. The final result still starts with 2, 4, 8, but the callback count changes. Array map handles all six numbers before array filter starts. The lazy iterator does pull → map → filter for one value at a time and stops after the fourth source value because three values have passed.

Step through lazy versus eager call order
Step 0 of 18Ready
Your turn: follow the blue line

Choose lazy or eager, then step. Watch whether one item moves through every station before the next item starts.

Running in
  1. script
Next: line 8
Click the blue line to take the next stepPop 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;  }}   .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(", "));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Switch the displayed run

Changing the mode starts a fresh recording with different call order and counts.

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.
Modern support and older environments

The standardized helpers in this lesson are available in current Chrome, Firefox, Safari, and Node 22. Libraries and long-lived apps should still feature-detect because older browsers may be present.

map, filter, take, drop and flatMap

The transforming helpers return new iterator objects. They do not produce a visible result until something consumes them.

Core iterator helper jobs
HelperLazy?ReturnsCommon use
map(callback)YesIteratorTransform each item as it is pulled
filter(callback)YesIteratorKeep values whose test is truthy
take(n) / drop(n)YesIteratorLimit or skip a prefix; n must be a non-negative number
flatMap(callback)YesIteratorReplace each item with zero or more yielded values
toArray()ConsumesArrayMaterialize the remaining values
reduce(callback, initial)ConsumesOne valueAccumulate the whole remaining iterator
some / every / findConsumes until decidedBoolean or valueShort-circuit searches
forEach(callback)ConsumesundefinedRun a side effect for each remaining item

map transforms each pulled value. filter keeps values when its callback returns truthy. take(n) lets the first n remaining values through; drop(n) skips the first n. Both validate their count. Negative values and NaN throw RangeError instead of silently doing something surprising.

flatMap is the one-to-many helper. Its callback returns an iterable, and the helper yields each inner value. Use it for things like splitting lines into words, turning a calendar day into zero or more events, or expanding a page of API results into individual rows.

One-shot consumption is realPop 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());

The first log prints [1, 2]. The second log prints [3], because the original iterator has already yielded the first two values. If you need to iterate twice, keep the original data (for example an array) or create a fresh iterator.

some, every, find, reduce, forEach and toArray

Consuming helpers are terminals. They pull values from the chain and return something other than another lazy iterator.

  • toArray() materializes every remaining value into an array.
  • reduce(callback, initial) combines all remaining values into one accumulator.
  • forEach(callback) runs a side effect and returns undefined.
  • some, every, and find short-circuit when the answer is known.

Short-circuiting is where laziness often shines. find does not care about later values once a match is found. some stops at the first truthy callback result, and every stops at the first falsy result. In an infinite sequence, those methods are safe only when the condition can eventually decide.

Do not forget the terminal

Iterator.from([1, 2, 3]).map(n => n * 2) describes work. It does not print or build an array. Add toArray(), find(...), reduce(...), or a for...of loop when you actually want values.

Helper playground

INTERACTIVE

Try a source, toggle helpers, then choose a terminal helper. The infinite counter is guarded: the playground refuses combinations that would try to consume forever. This is not a toy-only concern. Real streams, cursors, and generated sequences need the same discipline.

Helper playground: build a safe chain
Playground shapePop 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(4)  .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 uses predefined callbacks, not eval. Infinite counters must be bounded or short-circuited.

Notice how Set, arrays, generators, and infinite counters can share the same helper vocabulary once they are iterators. That is the practical payoff of the iteration protocol introduced in the previous lessons.

Iterator.concat, Iterator.zip, zipKeyed and proposals

FEATURE DETECT

At the time of writing, TC39 lists Sync Iterator helpers, Joint Iteration, and Iterator Sequencing as finished proposals. Those cover the standardized helper set plus ideas such as combining iterables, zipping them position-by-position, and sequencing iterables. Node 22 currently exposes the core helper methods but not Iterator.concat, Iterator.zip, or Iterator.zipKeyed. Treat finished-but-new APIs as feature-detected APIs.

Feature-detect 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 });

TC39 also lists iterator chunking, Iterator Includes, and Iterator Join as active Stage 3 proposals. The chunking proposal is where chunk-like and window-like operations are being explored. Until those names are actually present in your target runtimes, write small generator utilities or use a library, and label them as sketches rather than standard JavaScript.

A generator sketch, 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;}
Feature-detect the reader's browser
Feature checksPop 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 });
Sketch when chunking is unavailablePop 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 detected
  • map: not detected
  • filter: not detected
  • take: not detected
  • drop: not detected
  • flatMap: not detected
  • reduce: not detected
  • toArray: not detected
  • forEach: not detected
  • some: not detected
  • every: not detected
  • find: not detected

Newer or proposal helpers

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

Feature detection runs after mount to avoid server/browser mismatches.

This panel never decides during server render. It checks the actual browser after the lesson hydrates.

Where you'll use this

Iterator helpers are useful when the source is not naturally a small array or when stopping early saves work. Think log readers, generated IDs, pagination cursors, file lines, database-like cursors in a local tool, and data transformations that should stay readable without allocating every intermediate array.

Lazy or eager?
  • Find the first error line in a huge log
  • Show the next 20 items from an infinite counter
  • Sum every invoice in a finished monthly export
  • Render every search result already loaded in memory
  • Drop the first 100 sensor readings, then show 10
  • Sort products by price before taking page one
Try it yourself
0 of 6 correct

Sort each realistic task by the style that fits 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. Laziness is a tool for composition, memory use, and early stopping, not a rule that replaces arrays.

Common misconceptions

  • “map runs immediately.” Iterator map returns a lazy iterator. A terminal pulls it.
  • “Iterator helpers rewind like arrays.” Most iterators are one-shot. Consumed values are gone.
  • “take(-1) means take none.” Invalid counts throw RangeError.
  • “toArray is always harmless.” On an infinite source it never finishes. Limit or short-circuit first.
  • “Finished proposal means everywhere today.” Feature-detect newer names such as Iterator.zip.
  • “Lazy is always faster.” Sorting, grouping, and full reductions still need all relevant data.
Iterator helpers versus array methods
QuestionIterator helpersArray methods
When do callbacks run?When a consumer pulls valuesDuring the method call
Can the source be infinite?Yes, if a later helper stopsNo, arrays are finite materialized collections
Can you reuse the same result?Usually no; it is one-shotYes; arrays keep their elements
How do you get an array?Call toArray()You already have or receive an array

Practice exercises

4 EXERCISES
Exercise 1 · Warm-upPredict a lazy result

Run the program mentally. Type the exact text printed by line 10.

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 behavior

    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: missing terminal

      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 · ChallengeReduce after mapping

        Predict the total printed by this terminal helper chain.

        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 7Which statement best describes iterator helper laziness?

            Choose an answer to see the explanation.

          2. Question 2 of 7What does this one-shot iterator example print second?

            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 an invalid 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 consumes an iterator and returns an array?

            Choose an answer to see the explanation.

          5. Question 5 of 7What does this short-circuiting 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 treat Iterator.zip and Iterator.concat?

            Choose an answer to see the explanation.

          7. Question 7 of 7Which task is the best fit for a lazy iterator pipeline?

            Choose an answer to see the explanation.

          Key takeaways

          • Iterator.from adapts iterator-like values so helper methods can be used.
          • map, filter, take, drop, and flatMap are lazy and return iterators.
          • toArray, reduce, forEach, some, every, and find consume the remaining iterator.
          • One-shot iterators do not rewind; create a fresh iterator if you need another pass.
          • Feature-detect newer names such as Iterator.zip, chunking, includes, and join before relying on them.

          Iterator helpers are lazy method-based tools for adapting, transforming, limiting, searching, and consuming values from the iterator protocol.

          Up next: Async iteration & async generators.

          CompleteFrontend Clear concepts. Working examples.