Python Coroutines

A Python coroutine is a computation represented by a coroutine object, created when an `async def` function is called. It runs when awaited or scheduled, and can yield control while waiting so an event loop can make progress on other work.

In Python, defining a function with `async def` makes it a coroutine function. Calling that function does not run its body to completion; it creates a coroutine object representing work that can be awaited or scheduled.

A coroutine advances when something drives it. In typical `asyncio` programs, the event loop drives Tasks, and a Task can drive the coroutine it wraps. `await` pauses the current coroutine until the awaited operation produces a result, then resumes it.

Coroutines are useful when code must wait for operations such as network or file I/O without blocking the thread running the event loop. While one coroutine is waiting, the loop can run other ready work. This is cooperative concurrency, not automatic parallel execution.

A coroutine object should normally be awaited or scheduled; merely calling an `async def` function does not perform its work. A coroutine object is generally intended to be consumed once, so create a new one for each invocation.

Key points

Interview questions

What is a coroutine in Python, and when does its code run?

Calling a coroutine function creates a coroutine object but does not run the function body to completion. The coroutine must be awaited or scheduled, commonly by an asyncio Task. When it awaits an operation that suspends, control returns to the event loop, which can run other ready tasks. This supports concurrent I/O without requiring a separate thread for each operation.

Distinguish a coroutine, an awaitable, and an asyncio Task.

`async def` defines a coroutine function, and calling it produces a coroutine object. `await` suspends the current coroutine until the awaited object is ready, then resumes it with a result or exception. An awaitable is the broader category accepted by `await`; it includes coroutine objects and asyncio Tasks. A Task is an asyncio object that schedules a coroutine to progress on an event loop.

Does using coroutines make Python code parallel, and what can block progress?

Asyncio provides cooperative concurrency on an event loop, usually within one thread. A coroutine must yield at an await point that actually suspends before the loop can run other work; CPU-bound code or blocking calls can therefore stall the loop. `asyncio` does not make ordinary Python code parallel by itself. For CPU-bound work, use an appropriate process or thread strategy, while ensuring blocking I/O is handled without blocking the event-loop thread.

Can you await the same coroutine object more than once?

A coroutine object is generally a one-shot computation: once awaited to completion, it cannot be awaited again as if it were fresh. To perform the operation again, call the coroutine function again to obtain a new coroutine object. If the work needs to be scheduled and its result shared, retain and await the corresponding Task where appropriate. Unawaited coroutines may also trigger a runtime warning and represent work that never happened.