Learning Objectives

  • Understand the iterator protocol
  • Learn about iterable objects
  • Create custom iterators
  • Use Symbol.iterator

What is the Iterator Protocol?

The iterator protocol defines a standard way to produce a sequence of values. An object is an iterator when it implements a next() method that returns an object with value and done properties.

Iterator Interface

// Iterator must have a next() method
const iterator = {
  next() {
    return {
      value: any,    // The current value
      done: boolean  // true when iteration is complete
    };
  }
};

Creating a Custom Iterator

function createRangeIterator(start, end) {
  let current = start;
  
  return {
    next() {
      if (current <= end) {
        return {
          value: current++,
          done: false
        };
      } else {
        return {
          done: true
        };
      }
    }
  };
}

const iterator = createRangeIterator(1, 3);
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { done: true }

The Iterable Protocol

An object is iterable if it implements the @@iterator method (accessible via Symbol.iterator), which returns an iterator.

const iterable = {
  [Symbol.iterator]() {
    return iterator; // Returns an iterator
  }
};

Built-in Iterables

Many JavaScript objects are iterable by default:

// Arrays
const arr = [1, 2, 3];
for (const value of arr) {
  console.log(value); // 1, 2, 3
}

// Strings
const str = 'hello';
for (const char of str) {
  console.log(char); // h, e, l, l, o
}

// Maps
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
  console.log(key, value); // a 1, b 2
}

// Sets
const set = new Set([1, 2, 3]);
for (const value of set) {
  console.log(value); // 1, 2, 3
}

Creating a Custom Iterable

class Range {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }
  
  [Symbol.iterator]() {
    let current = this.start;
    const end = this.end;
    
    return {
      next() {
        if (current <= end) {
          return {
            value: current++,
            done: false
          };
        }
        return { done: true };
      }
    };
  }
}

const range = new Range(1, 5);

// Works with for...of
for (const num of range) {
  console.log(num); // 1, 2, 3, 4, 5
}

// Works with spread operator
console.log([...range]); // [1, 2, 3, 4, 5]

// Works with Array.from()
console.log(Array.from(range)); // [1, 2, 3, 4, 5]

Generators Simplify Iterators

Generators automatically implement the iterator protocol:

// Without generator (manual iterator)
class RangeManual {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }
  
  [Symbol.iterator]() {
    let current = this.start;
    const end = this.end;
    
    return {
      next() {
        if (current <= end) {
          return { value: current++, done: false };
        }
        return { done: true };
      }
    };
  }
}

// With generator (much simpler!)
class RangeGenerator {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }
  
  *[Symbol.iterator]() {
    for (let i = this.start; i <= this.end; i++) {
      yield i;
    }
  }
}

const range1 = new RangeManual(1, 3);
const range2 = new RangeGenerator(1, 3);

console.log([...range1]); // [1, 2, 3]
console.log([...range2]); // [1, 2, 3]

Iterator Consumption

for...of Loop

const iterable = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  }
};

for (const value of iterable) {
  console.log(value); // 1, 2, 3
}

Spread Operator

function* numbers() {
  yield 1;
  yield 2;
  yield 3;
}

const arr = [...numbers()];
console.log(arr); // [1, 2, 3]

const [a, b, c] = numbers();
console.log(a, b, c); // 1 2 3

Array Methods

function* range(start, end) {
  for (let i = start; i <= end; i++) {
    yield i;
  }
}

const arr = Array.from(range(1, 5));
console.log(arr); // [1, 2, 3, 4, 5]

// Map, filter, reduce work on arrays created from iterables
const doubled = Array.from(range(1, 5)).map(x => x * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

Real-World Example: Custom Collection

class LinkedList {
  constructor() {
    this.head = null;
  }
  
  add(value) {
    const node = { value, next: null };
    
    if (!this.head) {
      this.head = node;
    } else {
      let current = this.head;
      while (current.next) {
        current = current.next;
      }
      current.next = node;
    }
  }
  
  *[Symbol.iterator]() {
    let current = this.head;
    
    while (current) {
      yield current.value;
      current = current.next;
    }
  }
}

const list = new LinkedList();
list.add(1);
list.add(2);
list.add(3);

// Now iterable!
for (const value of list) {
  console.log(value); // 1, 2, 3
}

console.log([...list]); // [1, 2, 3]

Iterator Return and Throw

Iterators can optionally implement return() and throw() methods:

function* generatorWithCleanup() {
  try {
    yield 1;
    yield 2;
    yield 3;
  } finally {
    console.log('Cleanup!');
  }
}

const gen = generatorWithCleanup();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.return('early exit')); 
// Logs: Cleanup!
// Returns: { value: 'early exit', done: true }

Infinite Iterators

function* infiniteSequence() {
  let i = 0;
  while (true) {
    yield i++;
  }
}

const seq = infiniteSequence();
console.log(seq.next().value); // 0
console.log(seq.next().value); // 1
console.log(seq.next().value); // 2

// Take first 5 values
function* take(iterable, n) {
  let count = 0;
  for (const value of iterable) {
    if (count++ >= n) break;
    yield value;
  }
}

console.log([...take(infiniteSequence(), 5)]); // [0, 1, 2, 3, 4]

Key Takeaways

  • Iterator protocol: Objects with next() method
  • Iterable protocol: Objects with Symbol.iterator
  • ✅ Generators automatically implement both protocols
  • ✅ Custom iterables work with for...of, spread, etc.
  • ✅ Iterators can be infinite
  • ✅ Generators simplify iterator creation

Next Steps

Now let's explore the basic generator syntax in detail!