Skip to content

Instantly share code, notes, and snippets.

@gengwg
Created July 18, 2025 23:38
Show Gist options
  • Save gengwg/22d9916da06a981419b59145baabfbc3 to your computer and use it in GitHub Desktop.
Save gengwg/22d9916da06a981419b59145baabfbc3 to your computer and use it in GitHub Desktop.

Coroutines vs. Generators in Python

Both coroutines and generators use Python’s yield keyword, but they serve different purposes. Here’s a breakdown:


1. Generators

What?

  • A generator is a function that produces a sequence of values lazily (on-demand) using yield.
  • It pauses execution at each yield and resumes when the next value is requested (e.g., in a for loop).

Key Features

  • Single-directional: Data flows out of the generator (via yield).
  • Used for iteration: Ideal for large/streaming datasets (e.g., reading files line-by-line).
  • Stateful: Remembers its state between yields.

Example

def count_up_to(n):
    i = 1
    while i <= n:
        yield i  # Pauses here and returns `i`
        i += 1

# Usage
for num in count_up_to(5):
    print(num)  # Output: 1, 2, 3, 4, 5

2. Coroutines

What?

  • A coroutine is a generalization of generators that can consume data (via yield) and maintain state between executions.
  • They allow two-way communication: You can send data into a coroutine using .send().

Key Features

  • Bi-directional: Data flows in (via .send()) and out (via yield).
  • Used for cooperative multitasking: Lightweight concurrency (e.g., async I/O).
  • More complex: Can yield values while also receiving inputs.

Example

def echo():
    while True:
        received = yield  # Pauses and waits for a value via `.send()`
        print(f"Received: {received}")

# Usage
coro = echo()
next(coro)        # Prime the coroutine (runs until first `yield`)
coro.send("Hi")   # Output: "Received: Hi"
coro.send("Bye")  # Output: "Received: Bye"

3. Key Differences

Feature Generators Coroutines
Purpose Produce values lazily. Consume/produce values + pause/resume.
Data Flow One-way (yield out). Two-way (yield + .send() in).
Initialization Called directly (next(gen)). Must be "primed" with next(coro).
Use Case Iteration, memory efficiency. Concurrency, pipelines, stateful tasks.
Python Version Since Python 2.2. Enhanced in Python 2.5+.

4. Overlapping Concepts

  • Both use yield to pause/resume execution.
  • Coroutines are built on generators (a coroutine is a superset of a generator).
  • Modern Python uses async/await for coroutines (more readable than raw yield).

5. Practical Scenarios

When to Use Generators

  • Streaming large files.
  • Generating infinite sequences (e.g., Fibonacci numbers).
  • Memory-efficient pipelines.

When to Use Coroutines

  • Cooperative multitasking (e.g., event loops).
  • Stateful processing (e.g., parsing tokens).
  • Lightweight concurrency (pre-asyncio).

6. Modern Python (async/await)

  • Coroutines are now typically written with async def and await.
  • Replaces explicit .send()/yield with cleaner syntax:
    async def fetch_data():
        data = await some_io_operation()  # Pauses until I/O completes
        return data

Summary

  • Generators: Pull values out with yield (for iteration).
  • Coroutines: Push values in with .send() + pull values out (for concurrency).
  • Modern Python: Prefer async/await for coroutines.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment