Understanding generators in Python
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Generators are one of Python's most useful tools for working with sequences lazily. Instead of building all values up front, a generator produces them one at a time as iteration advances.
That small change has a big effect on memory use, performance characteristics, and the way data flows through a program.
What a Generator Is
A generator is an iterator created either by a function that uses yield or by a generator expression. When Python reaches yield, it returns a value to the caller and pauses the function state. On the next iteration, execution resumes exactly where it left off.
Basic example:
Output:
The function does not build a list [1, 2, 3]. It yields each number only when requested by the loop.
Why Generators Matter
The main advantage is lazy evaluation. A list computes and stores every element immediately. A generator computes the next element only when iteration asks for it.
That matters when:
- the sequence is very large
- values are expensive to compute
- data arrives from a file, socket, or stream
- you want to compose processing steps without temporary lists
For example, summing squares with a list creates all squares first:
Using a generator expression avoids building the intermediate list:
Both produce the same result, but the generator version usually uses less memory.
Generator Functions and State
When a normal function returns, its local state disappears. A generator function is different. It keeps enough state to continue later.
The local variable total survives across yield points. That is why generators are so useful for incremental computation.
Generator Expressions
Python also supports generator expressions, which look like list comprehensions without the outer brackets.
List comprehension:
Generator expression:
The first creates a list immediately. The second creates a generator object that yields values on demand.
You can inspect that behavior directly:
This prints 0, then 2, then 4.
Generators in Pipelines
Generators become especially powerful when chained together. Each stage can process one item at a time instead of materializing the full dataset at every step.
This style is common in data processing because it keeps each step focused and memory-friendly.
Generators Are Single-Use Iterators
One important property surprises many beginners: generators are consumed as you iterate them.
After the first pass, the generator is exhausted. If you need to iterate again, recreate it or store the values in a reusable collection such as a list.
Common Pitfalls
The biggest pitfall is expecting a generator to behave like a list. You cannot index it directly, and once consumed, it does not reset automatically.
Another issue is debugging. Because generators are lazy, bugs may appear later than expected, only when iteration actually happens.
Developers also sometimes choose generators for tiny fixed datasets where a list would be clearer. Laziness is useful, but it is not automatically the best choice for every piece of code.
Finally, remember that generator expressions are not magic performance boosts. They reduce memory pressure, but if the computation itself is expensive, that cost still exists.
Summary
- Generators produce values lazily, one item at a time.
- Use
yieldin a function or a generator expression to create them. - They are great for large sequences, streams, and processing pipelines.
- Generators preserve state between
yieldpoints but are single-use iterators. - Choose generators when lazy evaluation improves clarity or memory use, not just because they are available.

