Generators & Iterators in JavaScript

An iterator is an object that lets you go through a list of values one at a time. Think of it like a bookmark in a book — it remembers where you are, and each time you ask for the next item, it moves forward.

Real-world analogy: Like a vending machine. You press a button once and get one item. You press again and get the next one. You don't get all items at once — just one at a time, in order.

An iterator must have a next() method. Each call to next() returns an object with:

  • value — the current value
  • donetrue if there are no more values, false if there are more

javascript

// Manually building an iterator
function makeCounter(start, end) {
  let current = start;
  
  return {
    next: function() {
      if (current <= end) {
        return { value: current++, done: false };
      } else {
        return { value: undefined, done: true };
      }
    }
  };
}

const counter = makeCounter(1, 3);

console.log(counter.next()); // { value: 1, done: false }
console.log(counter.next()); // { value: 2, done: false }
console.log(counter.next()); // { value: 3, done: false }
console.log(counter.next()); // { value: undefined, done: true }

An iterable is any object that can be iterated. In JavaScript, arrays, strings, Sets, and Maps are all iterables.

For an object to be iterable, it must have a special method named Symbol.iterator. This method must return an iterator (an object with a next() method).

Built-in iterables: Arrays, Strings, Maps, Sets, NodeLists.

javascript

// Arrays are iterable
let fruits = ["apple", "banana", "cherry"];
let iterator = fruits[Symbol.iterator](); // Get the iterator

console.log(iterator.next()); // { value: "apple",  done: false }
console.log(iterator.next()); // { value: "banana", done: false }
console.log(iterator.next()); // { value: "cherry", done: false }
console.log(iterator.next()); // { value: undefined, done: true }

// Strings are also iterable — each character is a value
let greeting = "Hi!";
let strIterator = greeting[Symbol.iterator]();

console.log(strIterator.next()); // { value: "H", done: false }
console.log(strIterator.next()); // { value: "i", done: false }
console.log(strIterator.next()); // { value: "!", done: false }
console.log(strIterator.next()); // { value: undefined, done: true }

You can also make your own iterable by adding [Symbol.iterator]() to any object:

javascript

// Create an object that is iterable
let range = {
  from: 1,
  to: 5,
  
  [Symbol.iterator]() {
    let current = this.from;
    let last = this.to;
    
    return {
      next() {
        if (current <= last) {
          return { value: current++, done: false };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};

// Now we can loop over it
for (let num of range) {
  console.log(num); // 1, 2, 3, 4, 5
}

The for...of loop is the cleanest way to loop over any iterable. It works with arrays, strings, Sets, Maps, and any custom iterable.

Under the hood, for...of calls [Symbol.iterator]() to get the iterator, then keeps calling next() until done is true.

javascript

// for...of with an array
let colors = ["red", "green", "blue"];

for (let color of colors) {
  console.log(color);
}
// red
// green
// blue

// for...of with a string
let name = "Alice";

for (let char of name) {
  console.log(char);
}
// A, l, i, c, e

// for...of with a Set
let uniqueNumbers = new Set([1, 2, 3, 2, 1]);

for (let num of uniqueNumbers) {
  console.log(num);
}
// 1, 2, 3  (duplicates removed by Set)

// for...of with a Map
let scores = new Map([["Alice", 95], ["Bob", 87]]);

for (let [name, score] of scores) {
  console.log(name + ": " + score);
}
// Alice: 95
// Bob: 87

Important: for...of vs for...infor...in iterates over object keys, while for...of iterates over values of an iterable.

A generator is a special kind of function that can be paused and resumed. Normal functions run from start to finish in one go. A generator can stop in the middle, hand you a value, and then continue from where it left off when you ask it to.

Real-world analogy: Like a conveyor belt at a factory. The belt doesn't move all items to you at once. It brings one item, stops, and waits. When you're ready for the next one, the belt moves again.

Generators are useful when:

  • You need to produce a series of values lazily (only when asked)
  • You're working with infinite sequences
  • You want to pause execution and come back later

The yield keyword is how a generator pauses and hands back a value.

javascript

// A normal function — runs all the way through
function normalGreet() {
  console.log("Hello!");
  console.log("How are you?");
  console.log("Goodbye!");
}

normalGreet(); // Prints all three at once, no stopping

// A generator function — can be paused at each yield
function* greetGenerator() {
  console.log("Hello!");
  yield;  // Pause here
  console.log("How are you?");
  yield;  // Pause here
  console.log("Goodbye!");
}

let gen = greetGenerator(); // Does NOT run yet
gen.next(); // Runs until first yield: prints "Hello!"
gen.next(); // Continues: prints "How are you?"
gen.next(); // Continues to end: prints "Goodbye!"

Generator functions use function* (with a star). They use yield to pause and return a value.

Each call to .next() on a generator returns { value, done } — just like an iterator.

javascript

// Generator that yields values
function* fruitList() {
  yield "apple";
  yield "banana";
  yield "cherry";
}

let gen = fruitList();

console.log(gen.next()); // { value: "apple",  done: false }
console.log(gen.next()); // { value: "banana", done: false }
console.log(gen.next()); // { value: "cherry", done: false }
console.log(gen.next()); // { value: undefined, done: true }

// Generators work with for...of
for (let fruit of fruitList()) {
  console.log(fruit); // apple, banana, cherry
}

// Generator that yields computed values
function* multiplyBy(start, factor, count) {
  for (let i = 0; i < count; i++) {
    yield start * factor;
    start++;
  }
}

for (let val of multiplyBy(1, 3, 5)) {
  console.log(val); // 3, 6, 9, 12, 15
}

Generators can also produce infinite sequences — they only calculate the next value when you ask for it:

javascript

// An infinite counter — only calculates values on demand
function* infiniteCounter(start = 0) {
  while (true) {
    yield start++;
  }
}

let counter = infiniteCounter(10);

console.log(counter.next().value); // 10
console.log(counter.next().value); // 11
console.log(counter.next().value); // 12
// You can call this forever — it never runs out
// But it only calculates each value when you ask for it

Here are real-world situations where generators shine:

1. Lazy evaluation / large data sets — Don't calculate everything upfront. Only compute what's needed.

javascript

// Processing a huge range of numbers without creating the whole array
function* range(start, end) {
  for (let i = start; i <= end; i++) {
    yield i;
  }
}

// Only the numbers that pass the filter are processed
for (let num of range(1, 1000000)) {
  if (num % 7 === 0) {
    console.log(num); // First few multiples of 7
    if (num > 50) break; // Stop when done
  }
}
// vs creating a million-element array upfront — generators are much more memory-efficient

2. Infinite sequences — IDs, timestamps, and more.

javascript

// Auto-incrementing ID generator
function* createId() {
  let id = 1;
  while (true) {
    yield id++;
  }
}

const getId = createId();

const user1 = { name: "Alice", id: getId.next().value }; // id: 1
const user2 = { name: "Bob",   id: getId.next().value }; // id: 2
const user3 = { name: "Carol", id: getId.next().value }; // id: 3

3. Async-like control flow — Generators were the foundation for async/await before it was native in JavaScript. Libraries like co used generators to write async code that looked synchronous.

💡 Interview Tip: Generators are commonly asked about in senior-level JavaScript interviews. Understanding yield and how generators relate to iterators can set you apart.

  • Iterator: An object with a next() method that returns { value, done }. It lets you step through a sequence one item at a time.
  • Iterable: Any object that has a [Symbol.iterator]() method. Arrays, strings, Sets, and Maps are all iterables.
  • for...of: The easiest way to loop over any iterable. It automatically calls next() until done is true.
  • Generator function: Declared with function*. It can be paused mid-execution using yield.
  • yield: Pauses the generator and returns a value to the caller. The function resumes from that exact point on the next .next() call.
  • Lazy evaluation: Generators only produce values when asked. This makes them memory-efficient for large or infinite sequences.
  • Infinite sequences: Generators are perfect for sequences that have no natural end (IDs, timestamps, etc.).
  • Generators are iterators: A generator object is both an iterable and an iterator — it has [Symbol.iterator]() and next() built in.

💡 What's next? Head back to the Memory Management tutorial to understand how JavaScript handles memory automatically.

  • What is an iterator in JavaScript?
  • What does the next() method return on an iterator?
  • What is the difference between an iterator and an iterable?
  • What is Symbol.iterator and why is it important?
  • What is the difference between for...in and for...of?
  • What is a generator function? How is it different from a normal function?
  • What does function* mean in JavaScript?
  • What does the yield keyword do?
  • What is lazy evaluation? Why are generators useful for large data sets?
  • Can you create an infinite sequence with a generator? Give an example.
  • How do generators relate to async/await?
  • Are generators iterators? Can you use for...of with a generator?
  • What is the output of calling .next() after a generator is done (done: true)?