Skip to content

JavaScript Generators & Iterators — Lazy Sequences, Async Iteration, and Custom Iterables

DodaTech Updated 2026-06-29 5 min read

In this tutorial, you will learn about JavaScript Generators & Iterators. We cover key concepts, practical examples, and best practices to help you master this topic.

Iterators and generators are JavaScript's mechanism for producing sequences of values on demand — instead of computing everything up front, you compute each value as it's needed. This enables memory-efficient processing of large datasets, infinite sequences, and elegant async control flow.

DodaTech uses generators extensively for processing scan results, paginating through API responses, and building middleware pipelines.

What You'll Learn

  • The iterator protocol (Symbol.iterator)
  • Custom iterables (objects you can loop over)
  • Generator functions (function*)
  • Generator delegation (yield*)
  • Async iterators and generators
  • Real-world patterns with generators

The Iterator Protocol

Every iterable in JavaScript implements Symbol.iterator:

const arr = [10, 20, 30];
const iterator = arr[Symbol.iterator]();

console.log(iterator.next()); // { value: 10, done: false }
console.log(iterator.next()); // { value: 20, done: false }
console.log(iterator.next()); // { value: 30, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

// for...of uses this protocol internally:
for (const val of arr) {
  console.log(val);
}

Custom Iterables

// Range: iterate over a range of numbers
class Range {
  constructor(start, end, step = 1) {
    this.start = start;
    this.end = end;
    this.step = step;
  }

  [Symbol.iterator]() {
    let current = this.start;
    const end = this.end;
    const step = this.step;

    return {
      next() {
        if (current <= end) {
          const value = current;
          current += step;
          return { value, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
}

const nums = new Range(1, 5);
console.log([...nums]); // [1, 2, 3, 4, 5]

// Paginated API client (iterable)
class ApiPaginator {
  constructor(baseUrl, pageSize = 50) {
    this.baseUrl = baseUrl;
    this.pageSize = pageSize;
  }

  [Symbol.iterator]() {
    let page = 1;
    let hasMore = true;
    const baseUrl = this.baseUrl;
    const pageSize = this.pageSize;

    return {
      async next() {
        if (!hasMore) return { done: true };

        const response = await fetch(
          `${baseUrl}?page=${page}&limit=${pageSize}`
        );
        const data = await response.json();
        hasMore = data.hasMore;
        page++;
        return { value: data.items, done: false };
      }
    };
  }
}

Generator Functions

Generator functions (function*) simplify creating iterators:

function* countUpTo(max) {
  for (let i = 1; i <= max; i++) {
    yield i;
  }
}

const generator = countUpTo(3);
console.log([...generator]); // [1, 2, 3]

function* infiniteFibonacci() {
  let a = 0, b = 1;
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

const fib = infiniteFibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
// Take first 10 fibonacci numbers
const first10 = [...new Array(10)].map(() => fib.next().value);

Two-Way Communication

function* questionMachine() {
  const answer = yield "What is your name?";
  yield `Hello, ${answer}!`;
  const age = yield "How old are you?";
  yield `You are ${age} years old.`;
}

const qm = questionMachine();
console.log(qm.next().value);       // "What is your name?"
console.log(qm.next("Alice").value); // "Hello, Alice!"
console.log(qm.next().value);       // "How old are you?"
console.log(qm.next(30).value);     // "You are 30 years old."

Generator Delegation

function* letters() {
  yield "a";
  yield "b";
  yield "c";
}

function* combined() {
  yield 1;
  yield* letters();  // Delegates to letters()
  yield 2;
}

console.log([...combined()]); // [1, "a", "b", "c", 2]

// Practical: flatten nested arrays
function* flatten(arr) {
  for (const item of arr) {
    if (Array.isArray(item)) {
      yield* flatten(item);
    } else {
      yield item;
    }
  }
}

Async Iterators and Generators

// Async generator: yields Promises
async function* fetchPages(url, maxPages = 5) {
  for (let page = 1; page <= maxPages; page++) {
    const response = await fetch(`${url}?page=${page}`);
    const data = await response.json();
    yield data;
    if (!data.hasMore) break;
  }
}

// Consume with for-await-of
async function getAllItems() {
  const allItems = [];
  for await (const page of fetchPages("/api/items")) {
    allItems.push(...page.items);
  }
  return allItems;
}

// Async iterator protocol
class AsyncRange {
  constructor(start, end, delay = 100) {
    this.start = start;
    this.end = end;
    this.delay = delay;
  }

  [Symbol.asyncIterator]() {
    let current = this.start;
    const end = this.end;
    const delay = this.delay;

    return {
      async next() {
        await new Promise(r => setTimeout(r, delay));
        if (current <= end) {
          return { value: current++, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
}

(async () => {
  for await (const num of new AsyncRange(1, 3, 50)) {
    console.log(num); // 1, 2, 3 (with 50ms delay each)
  }
})();

Real-World Patterns

Middleware Pipeline

function* middlewarePipeline(middlewares, initialCtx) {
  let ctx = initialCtx;
  for (const mw of middlewares) {
    ctx = yield mw(ctx);
  }
  return ctx;
}

// Usage: DodaTech's scan pipeline
const pipeline = middlewarePipeline([
  (ctx) => ({ ...ctx, validated: true }),
  (ctx) => ({ ...ctx, scanned: true }),
  (ctx) => ({ ...ctx, reported: true }),
], { url: "https://example.com" });

Lazy Transform Sequences

function* map(iterable, fn) {
  for (const item of iterable) {
    yield fn(item);
  }
}

function* filter(iterable, predicate) {
  for (const item of iterable) {
    if (predicate(item)) {
      yield item;
    }
  }
}

function* take(iterable, n) {
  let count = 0;
  for (const item of iterable) {
    if (count >= n) break;
    yield item;
    count++;
  }
}

// Lazy pipeline — no intermediate arrays!
const numbers = new Range(1, 1000000);
const result = [
  ...take(
    filter(
      map(numbers, n => n * 2),
      n => n % 3 === 0
    ),
    5
  )
];
console.log(result); // [6, 12, 18, 24, 30]

Rate Limiter

function* rateLimiter(maxPerSecond) {
  const timestamps = [];
  while (true) {
    const now = Date.now();
    const windowStart = now - 1000;
    // Remove old timestamps
    while (timestamps.length && timestamps[0] < windowStart) {
      timestamps.shift();
    }
    if (timestamps.length < maxPerSecond) {
      timestamps.push(now);
      yield true;
    } else {
      yield false;
    }
  }
}

Practice Questions

  1. Write a generator that produces an infinite sequence of prime numbers (lazily computed).

  2. Implement a custom iterable PaginatedResults that fetches pages from an API and yields individual items.

  3. Write an async generator that reads a file line by line (Node.js readline).

  4. Implement zip as a generator (combine corresponding elements from two iterables).

  5. Write a generator that produces a Sliding Window of size N over an iterable.

Challenge: CSV Parser with Generators

Build a streaming CSV parser:

  • Accept an iterable of lines (from a file or network)
  • Parse headers on first line
  • Yield objects for each subsequent line
  • Handle quoted fields (with embedded commas and newlines)
  • Support async sources

This pattern powers DodaTech's bulk data import for customers uploading vulnerability scan results in CSV format.

Real-World Task: Infinite Scroll Data Source

Write a generator that simulates an infinite scroll API:

  • Fetches "pages" of results on demand
  • Yields individual items one at a time
  • Pre-fetches the next page when current page is 50% consumed
  • Handles errors gracefully (retry with backoff)
  • Cancels gracefully when consumer stops iterating

This is the exact pattern used in DodaTech's dashboard for displaying real-time scan events across thousands of assets.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro