Advanced Generators MCQ 15 Tricky Questions
Time: 20-30 mins Intermediate/Advanced

Tricky Python Generators MCQ Challenge

Test your mastery of Python generators with 15 challenging multiple choice questions. Covers yield vs return, generator expressions, coroutines, lazy evaluation, memory efficiency, yield from, and tricky edge cases that often trip up developers.

Lazy Evaluation

On-demand computation

Memory Efficiency

One item at a time

Infinite Sequences

Never-ending streams

Coroutines

Two-way communication

Mastering Python Generators: Advanced Concepts and Tricky Behaviors

Python generators are special functions that can pause and resume their execution, producing a sequence of values lazily. This MCQ test focuses on the tricky aspects of Python's generator system—yield vs return, generator expressions, coroutines with send() and throw(), yield from delegation, memory efficiency patterns, and common pitfalls that lead to unexpected behavior.

Generator Execution Flow:
Create Generator next() / send() Run to yield Pause & Return Value Wait for next() Resume after yield...
List Approach

Creates ALL items in memory

Memory: O(n)

Immediate computation

Generator Approach

Creates ONE item at a time

Memory: O(1)

Lazy (on-demand) computation

Advanced Generator Concepts Covered

  • yield vs return

    yield pauses function, return ends it; generators use yield

  • Lazy Evaluation

    Values computed only when needed (memory efficient)

  • Generator Expressions

    (x*2 for x in range(10)) - concise generator syntax

  • Coroutines

    Generators with send(), throw(), close() for two-way communication

  • yield from

    Delegate to subgenerator (PEP 380)

  • State Preservation

    Generators maintain local variables between calls

Why These Tricky Generator Questions Matter

Generators are fundamental to writing memory-efficient Python code, especially for large datasets, streaming data, and infinite sequences. Understanding the difference between regular functions and generator functions, mastering yield semantics, and using coroutines for complex control flow are skills that separate novice from expert Python developers. These questions test attention to subtle behaviors that can make or break generator-based designs.

Key Generator Insight

Generators use lazy evaluation—they produce values on-demand rather than computing all values upfront. This makes them memory efficient (O(1) memory for sequences vs O(n) for lists) but they can only be iterated once. The yield keyword pauses execution and returns a value; the generator's state (local variables, instruction pointer) is preserved between calls.

Pro Tip: Use yield from (PEP 380) to delegate to subgenerators. It simplifies generator composition and properly propagates send(), throw(), and close(). For example: yield from range(10) is equivalent to for i in range(10): yield i but cleaner.

Common Generator Patterns

# Basic Generator Function def count_up_to(n): i = 1 while i <= n: yield i # Pauses here, returns i i += 1 # Generator is created but not executed yet gen = count_up_to(5) print(next(gen)) # 1 - first yield print(next(gen)) # 2 - resumes after yield print(next(gen)) # 3 # Or use in loop: for num in count_up_to(3): print(num) # 1, 2, 3
# Generator Expression (like list comprehension but lazy) # List comprehension - eager evaluation squares_list = [x*x for x in range(1000000)] # Memory: ~8MB # Generator expression - lazy evaluation squares_gen = (x*x for x in range(1000000)) # Memory: negligible # Both can be used in for loops, but generator is memory efficient for square in squares_gen: if square > 100: break print(square)
# Coroutine with send() and yield def coroutine(): print("Coroutine started") while True: value = yield # Receives value from send() print(f"Received: {value}") coro = coroutine() next(coro) # Prime the coroutine (advance to first yield) coro.send(10) # Received: 10 coro.send(20) # Received: 20 coro.close() # Clean up

yield from Deep Dive

yield from (PEP 380) simplifies generator delegation:

# Without yield from def chain(*iterables): for it in iterables: for element in it: yield element # With yield from (cleaner!) def chain_simple(*iterables): for it in iterables: yield from it # Delegates to subiterator list(chain_simple('ABC', range(3))) # ['A', 'B', 'C', 0, 1, 2]

Generator Best Practices

Use for Large Data

Generators excel with large/infinite sequences where memory matters.

One-time Use

Generators are iterators - can be consumed only once. Store in list if needed multiple times.

Prefer Generator Expressions

Use (x for x in iter) over [x for x in iter] when result isn't needed immediately.