Learning Objectives

  • Apply generators to real-world problems
  • Build practical generator-based solutions
  • Understand when to use generators
  • Master common generator patterns

1. Data Processing Pipelines

function* readCSV(text) {
  const lines = text.split('\n');
  for (const line of lines) {
    yield line.split(',');
  }
}

function* parseRecords(rows) {
  const headers = rows.next().value;
  
  for (const row of rows) {
    const record = {};
    headers.forEach((header, i) => {
      record[header] = row[i];
    });
    yield record;
  }
}

function* filterRecords(records, predicate) {
  for (const record of records) {
    if (predicate(record)) {
      yield record;
    }
  }
}

// Usage
const csvData = 'name,age,city\nAlice,30,NYC\nBob,25,LA\nCharlie,35,NYC';
const rows = readCSV(csvData);
const records = parseRecords(rows);
const nycResidents = filterRecords(records, r => r.city === 'NYC');

for (const person of nycResidents) {
  console.log(person); // { name: 'Alice', age: '30', city: 'NYC' }, ...
}

2. Lazy Data Loading

class LazyCollection {
  constructor(data) {
    this.data = data;
  }
  
  *[Symbol.iterator]() {
    for (const item of this.data) {
      yield item;
    }
  }
  
  *map(fn) {
    for (const item of this) {
      yield fn(item);
    }
  }
  
  *filter(predicate) {
    for (const item of this) {
      if (predicate(item)) {
        yield item;
      }
    }
  }
  
  *take(n) {
    let count = 0;
    for (const item of this) {
      if (count++ >= n) break;
      yield item;
    }
  }
  
  toArray() {
    return [...this];
  }
}

// Usage - processes only what's needed
const numbers = new LazyCollection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
const result = numbers
  .filter(n => n % 2 === 0)
  .map(n => n * n)
  .take(3)
  .toArray();

console.log(result); // [4, 16, 36]

3. ID Generator

class IDGenerator {
  constructor(prefix = 'ID', start = 1) {
    this.prefix = prefix;
    this.current = start;
  }
  
  *[Symbol.iterator]() {
    while (true) {
      yield `${this.prefix}-${String(this.current++).padStart(6, '0')}`;
    }
  }
  
  next() {
    return this[Symbol.iterator]().next().value;
  }
}

const userIDs = new IDGenerator('USER', 1000);
console.log(userIDs.next()); // USER-001000
console.log(userIDs.next()); // USER-001001
console.log(userIDs.next()); // USER-001002

4. Tree Traversal

class TreeNode {
  constructor(value, children = []) {
    this.value = value;
    this.children = children;
  }
  
  // Depth-first traversal
  *depthFirst() {
    yield this.value;
    for (const child of this.children) {
      yield* child.depthFirst();
    }
  }
  
  // Breadth-first traversal
  *breadthFirst() {
    const queue = [this];
    
    while (queue.length > 0) {
      const node = queue.shift();
      yield node.value;
      queue.push(...node.children);
    }
  }
}

const tree = new TreeNode(1, [
  new TreeNode(2, [
    new TreeNode(4),
    new TreeNode(5)
  ]),
  new TreeNode(3, [
    new TreeNode(6),
    new TreeNode(7)
  ])
]);

console.log([...tree.depthFirst()]);   // [1, 2, 4, 5, 3, 6, 7]
console.log([...tree.breadthFirst()]); // [1, 2, 3, 4, 5, 6, 7]

5. Batch Processing

function* batch(iterable, size) {
  let batch = [];
  
  for (const item of iterable) {
    batch.push(item);
    
    if (batch.length === size) {
      yield batch;
      batch = [];
    }
  }
  
  if (batch.length > 0) {
    yield batch;
  }
}

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (const group of batch(numbers, 3)) {
  console.log(group);
}
// [1, 2, 3]
// [4, 5, 6]
// [7, 8, 9]
// [10]

6. Permutations Generator

function* permutations(array) {
  if (array.length <= 1) {
    yield array;
    return;
  }
  
  for (let i = 0; i < array.length; i++) {
    const rest = [...array.slice(0, i), ...array.slice(i + 1)];
    
    for (const perm of permutations(rest)) {
      yield [array[i], ...perm];
    }
  }
}

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

7. Retry with Backoff

function* retryBackoff(maxRetries = 3, baseDelay = 1000) {
  for (let i = 0; i < maxRetries; i++) {
    const delay = baseDelay * Math.pow(2, i);
    yield { attempt: i + 1, delay };
  }
}

async function fetchWithRetry(url) {
  for (const { attempt, delay } of retryBackoff(3, 1000)) {
    try {
      console.log(`Attempt ${attempt}`);
      const response = await fetch(url);
      return await response.json();
    } catch (error) {
      console.log(`Failed, waiting ${delay}ms`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  throw new Error('All retries failed');
}

8. Sliding Window

function* slidingWindow(iterable, size) {
  const window = [];
  
  for (const item of iterable) {
    window.push(item);
    
    if (window.length === size) {
      yield [...window];
      window.shift();
    }
  }
}

const numbers = [1, 2, 3, 4, 5];
for (const window of slidingWindow(numbers, 3)) {
  console.log(window);
}
// [1, 2, 3]
// [2, 3, 4]
// [3, 4, 5]

9. Unique Values

function* unique(iterable) {
  const seen = new Set();
  
  for (const item of iterable) {
    if (!seen.has(item)) {
      seen.add(item);
      yield item;
    }
  }
}

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

10. Merge Sorted Sequences

function* mergeSorted(...iterables) {
  const iterators = iterables.map(it => it[Symbol.iterator]());
  const values = iterators.map(it => it.next());
  
  while (values.some(v => !v.done)) {
    let minIndex = -1;
    let minValue = Infinity;
    
    values.forEach((v, i) => {
      if (!v.done && v.value < minValue) {
        minValue = v.value;
        minIndex = i;
      }
    });
    
    if (minIndex !== -1) {
      yield minValue;
      values[minIndex] = iterators[minIndex].next();
    }
  }
}

const seq1 = [1, 3, 5, 7];
const seq2 = [2, 4, 6, 8];
const seq3 = [0, 9, 10];

console.log([...mergeSorted(seq1, seq2, seq3)]);
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Key Takeaways

  • ✅ Generators excel at data pipelines
  • ✅ Perfect for lazy evaluation
  • ✅ Great for tree/graph traversal
  • ✅ Ideal for batch processing
  • ✅ Simplify complex iterations
  • ✅ Memory efficient for large datasets

Next Steps

Finally, let's explore performance optimization techniques with generators!