Skip to main content

JavaScript Generators & Iterators

JavaScript Lazy Sequences

Written by Published

A generator can represent a sequence that has no end, because it only computes what is asked for.

An array has to exist in memory all at once; a generator does not.

Values are produced one at a time, only when requested.

Example

Example

javascript

function* naturalNumbers() {
  let n = 1;
  while (true) {
    yield n;
    n++;
  }
}

const numbers = naturalNumbers();

console.log(numbers.next().value);
console.log(numbers.next().value);
console.log(numbers.next().value);

Three values were produced; infinitely more remain unpaused.

Why This Is Safe

The while (true) never actually blocks anything.

Execution pauses at each yield until next() is called again.

You only ever compute as many values as you actually ask for.

Syntax

Syntax

javascript

function* infinite() {
  while (true) {
    yield next();
  }
}

Never spread or use for of without a way to stop.

A take Helper

This is the safe way to pull a limited number of values out.

Example

Example

javascript

function* naturalNumbers() {
  let n = 1;
  while (true) {
    yield n;
    n++;
  }
}

function take(iterable, count) {
  const result = [];
  const iterator = iterable[Symbol.iterator]();
  for (let i = 0; i < count; i++) {
    result.push(iterator.next().value);
  }
  return result;
}

console.log(take(naturalNumbers(), 5).join(","));

The output is 1,2,3,4,5.

An Infinite Sequence of Anything

The values do not have to be numbers, or even follow a simple pattern.

Example

Example

javascript

function* cycle(values) {
  let i = 0;
  while (true) {
    yield values[i % values.length];
    i++;
  }
}

function take(iterable, count) {
  const result = [];
  const iterator = iterable[Symbol.iterator]();
  for (let i = 0; i < count; i++) {
    result.push(iterator.next().value);
  }
  return result;
}

console.log(take(cycle(["a", "b", "c"]), 7).join(","));

The output is a,b,c,a,b,c,a.

Never Spread an Infinite Generator

Spreading it, or a plain for of with no break, tries to reach the end.

Since there is no end, this hangs the program.

Example

Example

javascript

function* naturalNumbers() {
  let n = 1;
  while (true) {
    yield n;
    n++;
  }
}

let first = null;
for (const n of naturalNumbers()) {
  first = n;
  break;
}

console.log(first);

A break is what makes this safe here.

Filtering a Lazy Sequence

A second generator can transform values from the first, still lazily.

Example

Example

javascript

function* naturalNumbers() {
  let n = 1;
  while (true) {
    yield n;
    n++;
  }
}

function* evensOnly(source) {
  for (const n of source) {
    if (n % 2 === 0) yield n;
  }
}

function take(iterable, count) {
  const result = [];
  const iterator = iterable[Symbol.iterator]();
  for (let i = 0; i < count; i++) {
    result.push(iterator.next().value);
  }
  return result;
}

console.log(take(evensOnly(naturalNumbers()), 4).join(","));

The output is 2,4,6,8, computed only as far as needed.

Why Not Just Use a Huge Array

A million-element array uses memory whether or not you need all of it.

A generator uses almost none, because nothing is stored ahead of time.

Example

Example

javascript

function* naturalNumbers() {
  let n = 1;
  while (true) {
    yield n;
    n++;
  }
}

const iterator = naturalNumbers();

console.log(typeof iterator.next);
console.log(iterator.next().value);

Only the current position is kept in memory, nothing more.

Complete Example

Complete Example

html

<!DOCTYPE html>
<html>
<head>
  <title>JavaScript Lazy Sequences</title>
</head>
<body>

  <h1>Lazy Sequences</h1>

  <p id="out"></p>

  <script>
    function* naturalNumbers() {
      let n = 1;
      while (true) {
        yield n;
        n++;
      }
    }

    function take(iterable, count) {
      const result = [];
      const iterator = iterable[Symbol.iterator]();
      for (let i = 0; i < count; i++) {
        result.push(iterator.next().value);
      }
      return result;
    }

    document.getElementById("out").textContent =
      "First 10 numbers: " + take(naturalNumbers(), 10).join(", ");
  </script>

</body>
</html>

Try It Yourself

Run the above example in the Try It Editor.

Try more values:

Change the count to 20 and see the sequence extend safely.

Important Points

  • A generator can represent a sequence with no end.
  • Nothing runs away, because execution pauses at each yield.
  • A take helper safely pulls a limited number of values.
  • Never spread, or loop without break, over an infinite generator.
  • A generator uses far less memory than a large precomputed array.

Conclusion

Lazy sequences are one of the strongest reasons to reach for a generator.

The pattern of generating, then filtering, then taking a limited amount is common in real code.

The one rule to hold onto is: always have a way to stop.