What's the difference between a coroutine and a generator?
Quick Answer
Both are built on the same underlying mechanism (a suspendable function frame), but they serve different purposes. A generator produces a sequence of values one at a time via yield, consumed by iteration (for, next()). A coroutine (async def) is designed to be awaited. It represents a unit of asynchronous work that eventually produces one result, driven by an event loop rather than a for loop, and uses await instead of yield to suspend.
Detailed Answer
Same suspension mechanism, different intent
def gen(): # generator: produces a SEQUENCE of values
yield 1
yield 2
async def coro(): # coroutine: produces ONE eventual result
await asyncio.sleep(1)
return 42
Both gen() and coro() return objects representing suspended
computation rather than running immediately. Under the hood, CPython's
native coroutines (async def) are implemented with the same frame-
suspension machinery that powers generators (historically, asyncio was
even built directly on @types.coroutine-decorated generators before
native coroutine syntax existed).
How they're driven differs
# Generator: driven by iteration
for value in gen():
print(value)
# Coroutine: driven by the event loop, via await/asyncio.run
result = await coro() # inside another coroutine
result = asyncio.run(coro()) # or, at the top level
You can't for loop over a coroutine (it's not iterable in that sense),
and you can't await a plain generator (unless it's specifically
decorated as a generator-based coroutine, a legacy pattern superseded by
async def). Trying to iterate a coroutine directly, or await a plain
generator, raises a TypeError.
Purpose: many values vs. one eventual value
- A generator's job is to lazily produce a sequence:
yieldeach value, potentially infinitely many, consumed one at a time. - A coroutine's job is to represent a single asynchronous
operation that will eventually complete with one result (or raise).
Conceptually it's closer to a
Future/Promisethan to an iterator, even though it's implemented with similar suspension internals.
Async generators: a hybrid
async def async_range(n):
for i in range(n):
await asyncio.sleep(0) # yield control back to the event loop
yield i
async for i in async_range(5):
print(i)
Python also supports async generators (async def containing
yield), which combine both: they lazily produce a sequence and can
await between values, consumed with async for instead of a plain
for loop. These are used for streaming data over an async source, like
reading paginated results from an async database driver.
Coroutines and generators share the same
suspend/resume mechanism, but generators (yield, driven by for/next)
model lazily producing a sequence of values, while coroutines (await,
driven by the event loop) model a single asynchronous operation resolving
to one eventual result. Async generators combine both when you need a
lazily-produced sequence that can also await I/O between items.