Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

JavaScript (ES2026)  Module 10 – Iterators and generators

Iterator helpers: lazy map, filter, take and more

JavaScript iterator helpers: lazy map, filter, take, drop and flatMap, reduce and toArray, Iterator.from, early exit, and when to use them over array methods.

  • Intermediate
  • 18 minutes
  • Examples run with Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free lesson, Pro answers: 18 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Build lazy chains of operations on any iterator with the iterator helpers of ES2025
  • Use Iterator.from to give helpers to strings, plain iterators and other iterables
  • Predict how many values a chain of helpers pulls and when it closes its source
  • Choose between array methods and iterator helpers for a job

Before you start

What you’ll learn · Before you start

What you will learn

  • Build lazy chains of operations on any iterator with the iterator helpers of ES2025
  • Use Iterator.from to give helpers to strings, plain iterators and other iterables
  • Predict how many values a chain of helpers pulls and when it closes its source
  • Choose between array methods and iterator helpers for a job

Before you start

On this page

Iterator helpers are methods that every built-in iterator inherits from Iterator.prototype: map, filter, take, drop and flatMap, which return a new iterator, and reduce, toArray, forEach, some, every and find, which consume one. They read like array methods, but they are lazy. A chain such as naturals().filter(isEven).map(square).take(3) does nothing until something asks it for values, and then it pulls one value at a time through every step, so it works on endless sequences and stops as soon as it has enough. Arrays are not iterators, so array.values() or Iterator.from(array) comes first. The helpers arrived in ES2025 and run in Chrome 122, Firefox 131, Safari 18.4 and Node.js 22 or later, and in the engine of this page’s Run buttons. This lesson counts exactly how many values a chain pulls, shows when it closes its source and lists the places where a helper behaves differently from the array method with the same name.

Lazy chains on any iterator

Both halves of this program compute the squares of the first three even numbers. The first asks an endless generator through helpers; the second asks a million-element array through array methods. Each callback counts its calls:

Counting the work of a helper chain and an array chain JavaScript · lazy-pipeline.mjs
// The squares of the first three even numbers, asked of an endless iterator and of a million-element array.
const lazy = { produced: 0, filter: 0, map: 0 };
function* naturals() {
  for (let n = 1; ; n++) {
    lazy.produced += 1;
    yield n;
  }
}

const fromIterator = naturals()
  .filter((n) => {
    lazy.filter += 1;
    return n % 2 === 0;
  })
  .map((n) => {
    lazy.map += 1;
    return n * n;
  })
  .take(3)
  .toArray();
console.log(fromIterator, lazy);

// Arrays need a finite source, and each method runs over every element before the next method starts.
const eager = { filter: 0, map: 0 };
const numbers = Array.from({ length: 1_000_000 }, (_, i) => i + 1);
const fromArray = numbers
  .filter((n) => {
    eager.filter += 1;
    return n % 2 === 0;
  })
  .map((n) => {
    eager.map += 1;
    return n * n;
  })
  .slice(0, 3);
console.log(fromArray, eager);

Output

[ 4, 16, 36 ] { produced: 6, filter: 6, map: 3 }
[ 4, 16, 36 ] { filter: 1000000, map: 500000 }

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node lazy-pipeline.mjs

Diagram: toArray asks take, take asks map, map asks filter, filter asks the source; each value travels back up alone.toArray()collects what take hands uptake(3)after 3 values: done, and closes the chainmap(n => n * n)one call per value that reaches itfilter(n => n % 2 === 0)asks again until a value passesnaturals()makes 1, 2, 3 … only when askednext()next()next()next(), next() …1, then 2244

How a chain of helpers pulls one value at a time

Text description of the diagram

The diagram shows the chain `naturals().filter(n => n % 2 === 0).map(n => n * n).take(3).toArray()` from lazy-pipeline.mjs as five boxes, the consumer at the top and the source at the bottom.

Solid arrows point down: each stage asks the stage below it for a value with `next()`. `toArray()` asks `take(3)`, which asks `map`, which asks `filter`, which asks `naturals()`. `filter` keeps asking until a value passes its test: it receives 1, rejects it, asks again and receives 2.

Dashed arrows point up and carry one value: 2 goes from `filter` to `map`, which turns it into 4, and 4 goes up through `take` to `toArray()`. Only then does the chain ask for the next value. After three values, `take` reports done and closes the stages below it, so `naturals()` never makes the seventh number.

The helper chain asked the generator for 6 numbers, ran filter 6 times and map 3 times, and stopped. toArray() started the work: it asked take(3) for a value, take asked map, map asked filter, and filter kept asking the generator until a number passed its test. After the third square, take answered done and closed everything below it, so the endless generator never produced a seventh number.

The array chain produced the same answer after calling filter 1,000,000 times and map 500,000 times, and only because the array was finite. Array methods are eager: filter builds a complete new array before map sees its first element, and slice throws away almost all the work. Given an endless source, the array version could not start at all, because Array.from would never finish.

One value at a time through the chain

The order of the callbacks shows the difference directly:

The order in which callbacks run JavaScript · order.mjs
// The order in which the callbacks run: one value at a time through the chain, or each method over everything.
const viaHelpers = [];
[1, 2, 3, 4]
  .values()
  .filter((n) => {
    viaHelpers.push(`filter ${n}`);
    return n % 2 === 0;
  })
  .map((n) => {
    viaHelpers.push(`map ${n}`);
    return n * 10;
  })
  .forEach((n) => viaHelpers.push(`got ${n}`));
console.log('helpers:', viaHelpers.join(' → '));

const viaArrays = [];
[1, 2, 3, 4]
  .filter((n) => {
    viaArrays.push(`filter ${n}`);
    return n % 2 === 0;
  })
  .map((n) => {
    viaArrays.push(`map ${n}`);
    return n * 10;
  })
  .forEach((n) => viaArrays.push(`got ${n}`));
console.log('arrays: ', viaArrays.join(' → '));

Output

helpers: filter 1 → filter 2 → map 2 → got 20 → filter 3 → filter 4 → map 4 → got 40
arrays:  filter 1 → filter 2 → filter 3 → filter 4 → map 2 → map 4 → got 20 → got 40

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node order.mjs

With helpers, each value travels through the whole chain before the next one is read: 2 passes filter, is mapped to 20 and reaches forEach before filter sees 3. With arrays, every method finishes its pass over all elements before the next method begins. Results are the same for pure callbacks, but laziness changes when side effects happen, how much memory the intermediate steps need (helpers build no intermediate arrays) and how early a chain can stop.

The helpers, one by one

Every helper once JavaScript · helpers-tour.mjs
// Every helper once, on the runs scored in six overs.
const overs = () => [12, 0, 47, 3, 88, 4].values(); // a new iterator each time: helpers use up their source

// Lazy helpers return a new iterator.
console.log(overs().map((runs, i) => `over ${i + 1}: ${runs}`).toArray()); // the second argument counts from 0
console.log(overs().filter((runs) => runs >= 10).toArray());
console.log(overs().take(2).toArray(), overs().drop(4).toArray());
console.log(overs().flatMap((runs) => (runs > 40 ? [runs, 'big over'] : [runs])).toArray());

// Eager helpers consume the iterator and return a result.
console.log(overs().reduce((total, runs) => total + runs, 0));
console.log(overs().some((runs) => runs === 0), overs().every((runs) => runs >= 0), overs().find((runs) => runs > 40));
overs()
  .take(2)
  .forEach((runs) => console.log('forEach:', runs));

Output

[
  'over 1: 12',
  'over 2: 0',
  'over 3: 47',
  'over 4: 3',
  'over 5: 88',
  'over 6: 4'
]
[ 12, 47, 88 ]
[ 12, 0 ] [ 88, 4 ]
[ 12, 0, 47, 'big over', 3, 88, 'big over', 4 ]
154
true true 47
forEach: 12
forEach: 0

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node helpers-tour.mjs

Helper Returns Ends early?
map(fn), filter(fn), flatMap(fn) a new lazy iterator no; it follows its consumer
take(n), drop(n) a new lazy iterator take stops after n values and closes its source
reduce(fn, start), toArray(), forEach(fn) a value, an array, nothing no; they read to the end
some(fn), every(fn), find(fn) a boolean or the value found yes, at the first answer

Like their array namesakes, the callbacks receive a counter after the value: how many values came before, starting at 0, as the over 1 to over 6 labels show. reduce passes the running result first, then the value and the counter. Unlike array callbacks, none of them receives the collection itself, since an iterator has no array to pass. Notice the factory overs(): each line calls it for a new iterator, because a helper uses up the iterator it reads from.

Getting an iterator from any collection

Helpers are methods of iterators, so the first step is always to get one:

Iterators from arrays, Maps, Sets, strings and hand-made objects JavaScript · get-iterators.mjs
// Helpers live on iterators, not on arrays: how to get an iterator from each kind of collection.
const scores = [52, 17, 88];
console.log(typeof scores.take, typeof scores.values().take); // arrays have no take(); their iterators do

const runs = new Map([
  ['Asha', 52],
  ['Ravi', 17],
]);
console.log(runs.keys().map((name) => name.toUpperCase()).toArray());
console.log(runs.entries().filter(([, total]) => total >= 50).toArray());
console.log(new Set(['js', 'css']).values().map((tag) => `#${tag}`).toArray());
console.log(Iterator.from('a😀b').map((ch) => ch.codePointAt(0).toString(16)).toArray()); // a string, by code point

// A hand-made iterator does not inherit the helpers; Iterator.from wraps it.
const handMade = {
  i: 0,
  next() {
    return this.i < 3 ? { value: this.i++, done: false } : { value: undefined, done: true };
  },
};
console.log(typeof handMade.map);
console.log(Iterator.from(handMade).map((n) => n * 2).toArray());

// A class that extends Iterator gets them by inheritance.
class Countdown extends Iterator {
  constructor(from) {
    super();
    this.n = from;
  }
  next() {
    return this.n > 0 ? { value: this.n--, done: false } : { value: undefined, done: true };
  }
}
console.log(new Countdown(3).map((n) => `T-${n}`).toArray());

const gen = (function* () {
  yield 1;
})();
console.log(Iterator.from(gen) === gen); // already an Iterator: returned as it is

Output

undefined function
[ 'ASHA', 'RAVI' ]
[ [ 'Asha', 52 ] ]
[ '#js', '#css' ]
[ '61', '1f600', '62' ]
undefined
[ 0, 2, 4 ]
[ 'T-3', 'T-2', 'T-1' ]
true

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node get-iterators.mjs

  • An array has no take; its iterator does. array.values(), array.keys() and array.entries() give one, and so do the same methods of a Map or a Set.
  • A generator object already inherits from Iterator.prototype, so the helpers work on it directly, as in naturals().filter(…).
  • Iterator.from(value) turns anything iterable into a helper-ready iterator: a string, which it walks by code point, an array or any object with a [Symbol.iterator]() method. It also accepts an object that only has next(), like the hand-made iterator that has no map of its own, and wraps it. When the iterator already inherits from Iterator.prototype, as a generator does, Iterator.from returns it unchanged.
  • A class that extends Iterator and defines next() inherits every helper, as Countdown does. Iterator itself cannot be called with new; it exists to be extended.

Stopping early closes the source

find, some, every and take stop reading once they have their answer, and they tell the iterator below them by calling its return() method. The call passes down the whole chain, so a generator at the bottom runs its finally block:

Scanning a log until the first error JavaScript · log-scan.mjs
// Stopping early closes the source: find() and take() call return() back through the whole chain.
function* logLines() {
  let read = 0;
  try {
    for (let n = 1; n <= 100_000; n++) {
      read += 1;
      yield n === 4_217 ? `ERROR payment ${n} declined` : `INFO payment ${n} ok`;
    }
  } finally {
    console.log(`  source closed after ${read} lines`);
  }
}

const firstError = logLines().find((line) => line.startsWith('ERROR'));
console.log(firstError);

const firstIds = logLines()
  .filter((line) => line.startsWith('INFO'))
  .map((line) => Number(line.split(' ')[2]))
  .take(3)
  .toArray();
console.log(firstIds);

console.log(logLines().some((line) => line.includes('payment 2 ')));

Output

  source closed after 4217 lines
ERROR payment 4217 declined
  source closed after 3 lines
[ 1, 2, 3 ]
  source closed after 2 lines
true

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node log-scan.mjs

Version note

In the engine of the Run button, QuickJS (quickjs-emscripten 0.32.0), this file prints the same. That engine has a defect worth knowing when you edit the examples: when a helper such as map or filter is stopped early, by a take, find, some or every after it or by a break in the loop that reads it, it throws TypeError: not a function there if it reads from the iterator of an array, a Set, a Map or a string. Node.js and the browsers close such chains correctly, and chains that read from a generator are not affected, as in this file.

find read 4,217 lines, found the error on the last of them and closed the generator; the remaining 95,783 lines were never produced. The filter, map and take(3) chain read exactly 3 lines, and some stopped at line 2. For a generator that reads a real file or a network stream, this is what releases the file or the connection, which an array pipeline over the same data could not do before reading everything.

Log Pattern Analyzer (Group Millions of Log Lines into Templates) Before writing a scan like log-scan.mjs, group a large log into templates to see which kinds of lines it holds, errors first.

Where helpers differ from array methods

The names match the array methods, the rules do not always:

Traps when moving from arrays to iterators JavaScript · helper-traps.mjs
// Where iterator helpers differ from their array namesakes.
function attempt(label, run) {
  try {
    console.log(`${label}:`, run());
  } catch (error) {
    console.log(`${label}: ${error.name}`);
  }
}

attempt('reduce over nothing, no start value', () => [].values().reduce((a, b) => a + b));
attempt('reduce over nothing, start value 0', () => [].values().reduce((a, b) => a + b, 0));
attempt('take(-1)', () => [1, 2].values().take(-1));
attempt('array flatMap to strings', () => ['ab', 'cd'].flatMap((s) => s));
attempt('iterator flatMap to strings', () => ['ab', 'cd'].values().flatMap((s) => s).toArray());
attempt('iterator flatMap to arrays', () => ['ab', 'cd'].values().flatMap((s) => [...s]).toArray());

// One walk only: a helper shares its source, and a used helper is empty.
const source = [1, 2, 3].values();
const doubled = source.map((n) => n * 2);
console.log(source.next().value); // taken from the source, so doubled never sees it
console.log(doubled.toArray(), doubled.toArray());
console.log(JSON.stringify({ doubled: [1, 2].values().map((n) => n * 2) })); // an iterator is not data

Output

reduce over nothing, no start value: TypeError
reduce over nothing, start value 0: 0
take(-1): RangeError
array flatMap to strings: [ 'ab', 'cd' ]
iterator flatMap to strings: TypeError
iterator flatMap to arrays: [ 'a', 'b', 'c', 'd' ]
1
[ 4, 6 ] []
{"doubled":{}}

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node helper-traps.mjs

  • reduce without a start value throws a TypeError on an empty iterator, as it does on an empty array. Pass the start value, reduce(fn, 0), whenever the input may be empty.
  • take and drop check their argument at once: a negative number or NaN is a RangeError when the method is called, not when the chain runs.
  • flatMap flattens only what its callback returns as an iterator or an iterable object. The array flatMap keeps a returned string as one element, while the iterator flatMap refuses strings with a TypeError. Return [...string], or Iterator.from(string), to flatten characters.
  • A helper shares its source. source.next() took the 1 away from doubled, which then saw only 2 and 3. And once doubled has been read, it is empty: toArray() a second time gives [].
  • An iterator is not data. JSON.stringify writes it as {}, so call toArray() before storing or sending values.

Array methods or iterator helpers?

Array methods and iterator helpers
Criterion Array methodsIterator helpers
Works on arraysany iterator; any iterable through Iterator.from
When the work happens at once: each method over every elementon demand: one value at a time through the chain
Endless input impossiblefine, with take, find or some to stop
Memory for the steps a new array per stepone value in flight
Read again any number of timesonce; ask the source for a new iterator
Also available length, indexes, sort, reverse, findLast, sliceonly what the helpers offer; toArray() for the rest
When to choose Small or finite data that you read several times, sort or indexLarge, endless or expensive sequences, early exits and streaming

For a short array that you filter once, the array methods are just as good and often clearer. Reach for helpers when the input is a generator, a stream of lines, or anything you might not want to read to the end.

Support

The helpers are part of ES2025. Following MDN’s browser-compat-data 8.1.4, Iterator.prototype.map and the other ten methods, together with Iterator.from, are in Chrome 122, Firefox 131, Safari 18.4, Node.js 22.0.0, Deno 1.39 and Bun 1.1.31, so every current major browser has them. The examples on this page ran on Node.js 24.21.0 and in QuickJS (quickjs-emscripten 0.32.0), the engine of the Run buttons, with the same output. For an older runtime, check typeof Iterator === 'function' && typeof Iterator.prototype.map === 'function' and fall back to the generator functions of the previous lessons, which do the same work one yield at a time.

JavaScript & TypeScript Online Compiler Runs code in your own browser's JavaScript engine: paste lazy-pipeline.mjs and change take(3) to take(30) to watch the counts grow.

Exercises

Exercise · Medium · JavaScript

Rewrite three array pipelines with lazy iterator helpers

Each function in pipelines.mjs has its old array version in a comment. The array versions are correct for arrays, but Array.from never returns when the input is an endless generator, such as a live feed of payments. Rewrite each function with iterator helpers so that it works for any iterable, endless ones included, and reads no more of the input than its answer needs.

  • firstLarge(amounts, min, count) returns an array of the first count amounts that are at least min, each rounded to a whole number with Math.round.
  • firstLongWord(words, minLength) returns the first word with at least minLength characters, or undefined when a finite input has none.
  • runningTotals(amounts) returns an iterator of running totals: for 5, 10 and 20 it gives 5, 15 and 35. It must return at once, before any total is computed, and compute each total only when asked.

Starter code · pipelines.mjs

/**
 * The first `count` amounts of at least `min`, rounded to whole numbers.
 * @param {Iterable<number>} amounts
 * @param {number} min
 * @param {number} count
 * @returns {number[]}
 */
export function firstLarge(amounts, min, count) {
  // Array version: return Array.from(amounts).filter((a) => a >= min).map((a) => Math.round(a)).slice(0, count);
  return [];
}

/**
 * The first word with at least `minLength` characters, or undefined.
 * @param {Iterable<string>} words
 * @param {number} minLength
 * @returns {string | undefined}
 */
export function firstLongWord(words, minLength) {
  // Array version: return Array.from(words).find((word) => word.length >= minLength);
  return undefined;
}

/**
 * An iterator of running totals, computed one at a time.
 * @param {Iterable<number>} amounts
 * @returns {Iterator<number>}
 */
export function runningTotals(amounts) {
  // Array version:
  // const totals = [];
  // let total = 0;
  // for (const amount of Array.from(amounts)) totals.push((total += amount));
  // return totals.values();
  return [].values();
}
The sample tests · pipelines.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { firstLarge, firstLongWord, runningTotals } from './pipelines.mjs';

/** The first `count` values of an iterator, with next(). */
function first(iterator, count) {
  const out = [];
  while (out.length < count) {
    const step = iterator.next();
    if (step.done) break;
    out.push(step.value);
  }
  return out;
}

/** A one-pass source of the given values (a generator, like a real feed). */
function* source(...values) {
  yield* values;
}

/** An endless feed that repeats `values` and counts how many it has produced. */
function feed(values) {
  const stats = { produced: 0 };
  function* run() {
    for (let i = 0; ; i++) {
      stats.produced += 1;
      yield values[i % values.length];
    }
  }
  return { items: run(), stats };
}

test('firstLarge keeps the first large amounts, rounded', () => {
  assert.deepEqual(firstLarge(source(120.4, 80, 250.6, 99.5, 300), 100, 2), [120, 251]);
  assert.deepEqual(firstLarge(source(120.4, 80, 250.6), 100, 0), []);
  assert.deepEqual(firstLarge([5, 50, 500.2], 40, 5), [50, 500]);
});

test('firstLarge on an endless feed reads only what it needs', () => {
  const { items, stats } = feed([10, 99.6, 20, 150]);
  assert.deepEqual(firstLarge(items, 50, 3), [100, 150, 100]);
  assert.equal(stats.produced, 6);
});

test('firstLongWord finds the first long word, or nothing', () => {
  assert.equal(firstLongWord(source('ok', 'iterator', 'helpers'), 5), 'iterator');
  assert.equal(firstLongWord(['a', 'bb'], 3), undefined);
  const { items, stats } = feed(['map', 'take', 'reduce', 'flatMap']);
  assert.equal(firstLongWord(items, 6), 'reduce');
  assert.equal(stats.produced, 3);
});

test('runningTotals adds up as it goes', () => {
  assert.deepEqual(first(runningTotals([5, 10, 20]), 10), [5, 15, 35]);
  assert.deepEqual(first(runningTotals([]), 10), []);
});

test('runningTotals is lazy', () => {
  const { items, stats } = feed([1, 2, 3]);
  const totals = runningTotals(items);
  assert.equal(stats.produced, 0);
  assert.deepEqual(first(totals, 4), [1, 3, 6, 7]);
  assert.equal(stats.produced, 4);
});
A hint

Arrays have no helpers, but Iterator.from(iterable) gives you an iterator that has them for any input. take(count) stops after count values, find stops at the first match, and for runningTotals, keep the total in a variable outside the callback: Iterator.from(amounts).map((amount) => (total += amount)) returns at once.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 What does order.mjs print?

    What does this program print? Choose one answer.

    // The order in which the callbacks run: one value at a time through the chain, or each method over everything.
    const viaHelpers = [];
    [1, 2, 3, 4]
      .values()
      .filter((n) => {
        viaHelpers.push(`filter ${n}`);
        return n % 2 === 0;
      })
      .map((n) => {
        viaHelpers.push(`map ${n}`);
        return n * 10;
      })
      .forEach((n) => viaHelpers.push(`got ${n}`));
    console.log('helpers:', viaHelpers.join(' → '));
    
    const viaArrays = [];
    [1, 2, 3, 4]
      .filter((n) => {
        viaArrays.push(`filter ${n}`);
        return n % 2 === 0;
      })
      .map((n) => {
        viaArrays.push(`map ${n}`);
        return n * 10;
      })
      .forEach((n) => viaArrays.push(`got ${n}`));
    console.log('arrays: ', viaArrays.join(' → '));
    Show the answer to question 1

    Answer: it prints

    helpers: filter 1 → filter 2 → map 2 → got 20 → filter 3 → filter 4 → map 4 → got 40
    arrays:  filter 1 → filter 2 → filter 3 → filter 4 → map 2 → map 4 → got 20 → got 40

    Helpers pull one value at a time through the whole chain, so 2 reaches forEach before filter sees 3. Array methods finish each pass over all elements before the next method starts. map only ever sees the values that passed filter, in both versions.

  2. Question 2 of 5 naturals() yields 1, 2, 3 … for ever. How many numbers does it produce for naturals().filter((n) => n % 3 === 0).take(2).toArray()?

    Type a number.

    Show the answer to question 2

    Answer: 6 numbers

    filter asks for numbers until one passes: 3 is the first multiple of 3 and 6 the second. After the second value, take reports done without asking for more, so the generator produced exactly 1 to 6.

  3. Question 3 of 5 What happens here?

    Read the code, then choose one answer.

    const scores = [52, 17, 88];
    console.log(scores.take(2));
    Show the answer to question 3

    Answer: A TypeError, because arrays have no take method; scores.values().take(2) works

    The helpers are methods of iterators, inherited from Iterator.prototype, and an array is not an iterator. scores.take is undefined, so calling it throws a TypeError. Ask the array for an iterator first, with values() or Iterator.from(scores).

  4. Question 4 of 5 What does ['ab', 'cd'].values().flatMap((s) => s).toArray() do?

    Choose one answer.

    Show the answer to question 4

    Answer: It throws a TypeError, because the iterator flatMap refuses strings

    The iterator flatMap flattens only iterators and iterable objects returned by its callback and rejects primitives, strings included. The array flatMap would keep each string as one element. Return [...s] to flatten the characters.

  5. Question 5 of 5 Which of these return a new lazy iterator instead of consuming the one they are called on?

    Choose every answer that is right.

    Show the answer to question 5

    Answer:

    • map
    • flatMap
    • drop

    map, filter, take, drop and flatMap return a new iterator that does its work only when asked. reduce, toArray, forEach, some, every and find read values straight away and return a result.

Interview questions

2 to 5 years · What is the difference between Array.prototype.map and Iterator.prototype.map?

Array.prototype.map is eager: it calls the callback for every element at once and returns a new array of the same length. Iterator.prototype.map is lazy: it returns a new iterator and calls the callback only when a consumer asks that iterator for its next value, one value at a time. So the iterator version works on endless sequences, holds no intermediate array and does no work for values nobody reads, for example after take(3) or find. In exchange its result is an iterator, not an array: it can be read once, has no length or indexes, and needs toArray() or a spread to become an array. Arrays have no iterator map of their own, so you call it on array.values() or Iterator.from(array). Both pass the callback a value and an index-like counter.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    A search box filters 500,000 orders on every keystroke with orders.filter(matches).map(toRow).slice(0, 20). Would iterator helpers make it faster? Estimate the callback calls of each version when 10 % of orders match, and say what you would measure before changing anything.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A pull request replaces arr.map(f) with arr.values().map(f).toArray() across the code base, claiming that lazy code is faster. Review it: which call sites can get faster, which change behaviour or break, and what would you ask the author for instead?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Iterator helpers live on Iterator.prototype: map, filter, take, drop and flatMap return lazy iterators; reduce, toArray, forEach, some, every and find consume.
  • A helper chain pulls one value at a time through every step and does no work until a consumer asks: the first three even squares cost 6 source values instead of a million.
  • Arrays are not iterators: start with array.values() or Iterator.from(iterable), which also wraps strings and hand-made iterators.
  • take, find, some and every close the source with return() when they stop, so a generator’s finally runs.
  • Unlike the array version, iterator flatMap rejects strings, a helper can be read only once, and an iterator is not data for JSON.stringify.
  • Use array methods for small data you index, sort or reread; use helpers for large, endless or expensive sequences.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.