ASAmol Shukla
Projects
Courses
Prompts
Skills
Contact
Resume
Course Outline
Syllabus Overview

Complete Python Course: From Zero to Professional

Courses/Complete Python Course: From Zero to Professional/Lesson 22: Iterators, Generators & itertools
45 mins lesson duration•9 mins read

Lesson 22: Iterators, Generators & itertools

Lazy iteration with yield, memory-efficient pipelines, and the itertools toolbox.

Iteration Without Memory Blowups

A generator produces values one at a time using yield. Unlike a list, it does not store everything in memory at once — perfect for huge or even infinite sequences.

This is the lesson where your Python goes from "works on small data" to "handles real-world data". Generators are the reason Python can process files larger than RAM, stream infinite sequences, and feed machine-learning pipelines without memory crashes.

What You'll Learn in This Lesson

  • Write generators with yield
  • Understand lazy evaluation
  • Use generator expressions
  • Apply itertools building blocks

yield — Pause and Resume

When a function contains yield, calling it returns a generator object. Each next() runs the code until the next yield, then pauses — saving all local state:

def countdown(n):
    while n >= 0:
        yield n
        n -= 1

for num in countdown(3):     # 3 2 1 0
    print(num)

Mental model: a generator is a book that reveals one page at a time. You read a page, close the book, and reopen exactly where you left off. The bookmark (local state) is saved.

Watch the difference between return and yield:

return yield
Function ends, one value handed back Function pauses, value handed out, resumes later
Caller gets the value immediately Caller gets a generator object to pull from
State is lost Local state is saved between yields

Why Generators Matter

Processing a 10 GB log file: a list loads all 10 GB into memory; a generator streams line by line using kilobytes.

big_list = [x * 2 for x in range(10_000_000)]   # builds 10M items — ~89 MB
big_gen  = (x * 2 for x in range(10_000_000))   # builds nothing yet — 112 bytes

Data pipelines, streaming, and AI training loops all rely on this lazy pattern.

List Generator
Builds all items Immediately Lazily
Memory O(n) O(1)
Can be iterated again Yes No (consumed)
Indexable Yes No

The one-shot rule: a generator is a conveyor belt, not a warehouse. Once you've taken every item, the belt is empty — iterate again and you get nothing. If you need two passes, convert to a list (and pay the memory cost) or rebuild the generator.


Generator Expressions

(x * 2 for x in range(10)) — a lazy comprehension. Pass it straight into sum(), max(), or a loop:

total = sum(x * x for x in range(1, 101))   # 338350 — no giant list

List vs generator expression — which to use?

Use a list [...] Use a generator (...)
You need the items multiple times Single pass is enough
You need indexing Passing to sum/max/min/any
Small data Large or infinite data
You need to inspect the result You only need the aggregate

itertools — The Toolbox

itertools ships with powerful iteration building blocks:

Tool What it does Example
islice(gen, n) Take the first n items Peek at infinite generators
chain(a, b) Combine sequences list(chain([1,2],[3,4]))
count() Infinite counter count(10, 2) → 10, 12, 14...
cycle(seq) Repeat forever cycle("AB") → A B A B...
product(a, b) Cartesian product product("AB", [1,2])
groupby(data) Group consecutive items Log aggregation
from itertools import islice, chain
print(list(islice((x for x in range(100) if x % 2 == 0), 5)))  # first 5 evens
print(list(chain([1, 2], [3, 4])))                              # [1, 2, 3, 4]

Peeking at an infinite sequence safely:

from itertools import count, islice
evens = count(0, 2)                          # 0, 2, 4, 6, ... forever
print(list(islice(evens, 5)))                # [0, 2, 4, 6, 8] — take 5, no crash

Mental model: islice is a "take n items and stop" guard — the only safe way to look at an infinite generator.


Real-World Generator Patterns

  • Reading huge files: for line in open("big.log") — one line in memory at a time (Lesson 20).
  • API pagination: a generator that fetches the next page only when asked.
  • Infinite data: count(), cycle() for round-robin load balancing and game logic.
  • Pipelines: chain generators — clean(parse(raw(f))) — each stage lazy, total memory O(1).

Common Mistakes to Avoid

  • Mistake: Iterating a generator twice and getting nothing the second time — Fix: generators are one-shot; rebuild or convert to a list if you need two passes.
  • Mistake: Mixing yield and return value in the same function — Fix: in a generator, return ends iteration (and return value is invalid); use yield for values.
  • Mistake: Materializing huge lists when a generator would do — Fix: reach for (expr for ...) or itertools.
  • Mistake: Forgetting the parentheses in a generator expression passed to a function — Fix: sum(x for x in ...) is fine without extra parens when it's the only argument.
  • Mistake: Calling len() or indexing a generator — Fix: generators have no length and no indexes; convert to a list first if you need them.

Professional Tips & Tricks

  • Use generators for file lines, API pagination, and any stream — never materialize huge lists.
  • Prefer (expr for ...) over [expr for ...] when passing to sum/max/any.
  • itertools.islice lets you peek at infinite generators safely.
  • Chain lazy stages into pipelines: each stage yields, total memory stays O(1).
  • Remember the one-shot rule: rebuild generators for a second pass.

Key Takeaways

  • yield creates a lazy, stateful generator.
  • Generators use O(1) memory and are one-shot.
  • Generator expressions (expr for ...) feed into sum/max/any.
  • itertools provides islice, chain, product, and more.
  • islice makes infinite generators safe to peek at.

Next up: JSON & working with real-world data.

Interactive Lesson Code Snippet
# Generator: lazy Fibonacci sequence
def fibonacci(limit):
    a, b = 0, 1
    while a <= limit:
        yield a
        a, b = b, a + b

print("Fibonacci up to 50:")
for num in fibonacci(50):
    print(num, end=" ")
print()

# Memory comparison
big_list = [x * 2 for x in range(10_000_000)]   # builds 10M items
big_gen  = (x * 2 for x in range(10_000_000))   # builds nothing yet
import sys
print("List size (MB):", sys.getsizeof(big_list) / 1e6)
print("Generator size (bytes):", sys.getsizeof(big_gen))

# itertools tools
from itertools import islice, chain
print("First 5 evens:", list(islice((x for x in range(100) if x % 2 == 0), 5)))
print("Chained:", list(chain([1, 2], [3, 4])))
Language: python

Lesson Code (Python)

# Generator: lazy Fibonacci sequence
def fibonacci(limit):
    a, b = 0, 1
    while a <= limit:
        yield a
        a, b = b, a + b

print("Fibonacci up to 50:")
for num in fibonacci(50):
    print(num, end=" ")
print()

# Memory comparison
big_list = [x * 2 for x in range(10_000_000)]   # builds 10M items
big_gen  = (x * 2 for x in range(10_000_000))   # builds nothing yet
import sys
print("List size (MB):", sys.getsizeof(big_list) / 1e6)
print("Generator size (bytes):", sys.getsizeof(big_gen))

# itertools tools
from itertools import islice, chain
print("First 5 evens:", list(islice((x for x in range(100) if x % 2 == 0), 5)))
print("Chained:", list(chain([1, 2], [3, 4])))

Console Output

Fibonacci up to 50:
0 1 1 2 3 5 8 13 21 34
List size (MB): 89.5
Generator size (bytes): 112
Chained: [1, 2, 3, 4]

Code Visualization Tips

  • 🧠Picture yield as 'pause and bookmark' — next() reopens the book at the bookmark.
  • 🧠Visualize a generator as a water pipe: only the current drop exists at any moment.
  • 🧠Compare sys.getsizeof(list) vs generator to SEE the memory difference.

Professional Tips & Tricks

  • ⚡Use generators for file lines, API pagination, and any stream — never materialize huge lists.
  • ⚡Prefer (expr for ...) over [expr for ...] when passing to sum/max/any.
  • ⚡itertools.islice lets you peek at infinite generators safely.

Python Code Judge & Practice Arena

LeetCode Style

Run real Python 3.12 WebAssembly code directly in your browser against automated test suites.

Solved:0 / 20
0 / 370 XP
Challenges:
Problem 1 of 20

Problem 1: Floating-Point frange 1

Easy+10 XP
Write a generator function `frange_1(start, stop, step)` that yields floating point values from start up to stop (exclusive).
Sample Test Cases:
Input: list(frange_1(0.0, 1.0, 0.25))
Expected: [0.0, 0.25, 0.5, 0.75]
Input: list(frange_1(0, 3, 1))
Expected: [0.0, 1.0, 2.0]
main.pyPython 3.12 (WASM)
1
2
3
4
5
6
7
8
9
10
11
12
Press Run Code to test or Submit to verify test cases

Test Your Knowledge

Instant feedback

Quick Check: Lesson 22: Iterators, Generators, `yield` & Itertools

1 / 20
What does the `yield` keyword do inside a function? def count_up(): yield 1 yield 2 yield 3

Up next · Continue learning

JSON & Working with Data

Serialize Python objects to JSON, load API data, and build real-world data workflows.

9 mins read45 mins
Start next lesson
Previous: Exception Handling — Fail GracefullyNext: JSON & Working with Data
Made withbyAmol Shukla·amolshukla.online
ASAmol Shukla

AI Developer, Trainer & Agentic AI Expert building practical learning systems and real-world AI applications.

Explore

  • Projects
  • Courses
  • Prompts
  • Skills
  • Contact
  • Experience
  • Blogs

Connect

  • Resume
  • Contact
© 2026 Amol Shukla·Created withbyamolshukla.online
Back to top