04 / 04

What is a generator expression, and how does it differ from a list comprehension in terms of memory usage?

Difficulty: 4/10
Comprehensions & Generator Expressions, Generator Expressions, Lazy vs Eager Evaluation

Generator expressions: lazy vs eager evaluation

A generator expression has the same syntax as a list comprehension but uses parentheses: (expr for x in it if cond). Instead of building the whole list, it creates a generator object, a lazy iterator that computes one item each time it is asked for the next value and remembers where it left off. A list comprehension is eager: it computes every element immediately and stores them all, so memory is O(n); the generator object itself is a small constant size, and memory use is O(1) as long as the consumer processes items one at a time.

javascript

Why this matters: lazy evaluation lets you process data bigger than memory (lines of a huge file, database rows, infinite sequences), lets pipelines stop early (any, all, next, itertools.islice never compute the rest), and avoids allocating a big intermediate list just to feed sum, max, min, join or a for loop. This is why sum(x * x for x in data) is preferred over sum([x * x for x in data]).

Trade-offs: a generator is single-use (a second iteration yields nothing and no error), supports neither len() nor indexing nor slicing, and cannot be rewound. If you need multiple passes, random access, a length, or printing the contents, use a list or tuple. For small data a list comprehension is often faster, since per-item generator resumption has overhead, so don't use generators purely out of habit. Laziness also changes when things happen: exceptions and side effects occur at consumption time, far from where the expression is written, which complicates debugging, and free variables are looked up when the item is computed (see the n example), not when the generator was created. Only the first iterable of the outermost for clause is evaluated immediately.

Common mistakes: wrapping a generator in list() immediately and losing the benefit; returning a generator that depends on a file or connection that has already been closed by a with block (the generator runs after the file is closed); and iterating the same generator twice. For more complex logic, a generator function with yield is the next step up and shares the same lazy semantics; itertools provides many building blocks for pipelines.

Scenario Questions

0-2 years experience

  1. 1Which uses more memory: [x * 2 for x in range(10**7)] or (x * 2 for x in range(10**7))? Why?
  2. 2What happens if you iterate over the same generator expression twice?

2-5 years experience

  1. 1You need the sum of squares of 100 million numbers. Show it with a generator expression and explain why the parentheses don't need to be doubled.
  2. 2A pipeline of generator expressions raises ZeroDivisionError far from where it was defined. Why does that happen, and how do you debug it?

5-8 years experience

  1. 1When is a list comprehension better than a generator expression, even for fairly large data (multiple passes, len, indexing, parallel processing)?
  2. 2A generator expression references a variable n that is changed after the generator was created. What value does the generator see, and which parts of it are evaluated eagerly versus lazily?

8+ years experience

  1. 1Design a streaming ETL job over a huge file using chained generators. How do you handle resource cleanup, exceptions mid-stream and testing?
  2. 2Compare generator expressions, generator functions with yield, and itertools pipelines in terms of performance and expressiveness. What are the limits of generators (no rewinding, pickling)?

Follow-up Questions

  • Why can a generator be iterated only once, and how would you get a restartable stream?
  • What is the difference between a generator expression and a generator function with yield?
Share

Share via WhatsApp, X, Facebook, LinkedIn or copy link. Open Graph preview enabled.