What should I decorate with asyncio.coroutine for async operations?
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
The short modern answer is: usually nothing. @asyncio.coroutine was the old way to mark a generator-based coroutine, and new async code should normally use async def instead.
The historical rule was simple, though. You decorated generator functions that cooperatively yielded control back to the event loop with yield from. You did not decorate ordinary synchronous helpers, CPU-bound work, or functions that still blocked the thread.
What @asyncio.coroutine Was For
Before native async and await syntax became the normal style, asyncio used generator-based coroutines. The decorator told Python and the event loop that the function was meant to behave like a coroutine.
Conceptually, it was for functions that:
- performed non-blocking asynchronous steps
- yielded control with
yield from - were intended to be awaited by other coroutine code
In that legacy style, this was the kind of function you would decorate:
That example uses modern syntax because it is the recommended runnable form today. In older code, the same idea would have been expressed with a decorated generator coroutine instead of async def.
What You Should Not Decorate
A normal synchronous helper does not become non-blocking just because you add a coroutine decorator. If the function still performs blocking I/O, calls time.sleep, or burns CPU in a long loop, decorating it does not make it safe for the event loop.
That means you should not use coroutine syntax or decorators for:
- plain utility functions that return immediately
- blocking database or network calls that do not integrate with asyncio
- CPU-heavy work that should run in another thread or process
For blocking work in modern asyncio code, use tools such as asyncio.to_thread or an executor instead of pretending the function is asynchronous.
The key idea is that async markers belong on functions that actually participate in cooperative asynchronous scheduling.
Think in Terms of Awaitable Boundaries
The best mental model is to decorate or define as async only the functions that form awaitable boundaries in your program. Those are the functions that other coroutine code should be able to await because they eventually yield control during I/O or another async step.
For example, in modern code you might write:
- '
async def read_message(...)' - '
async def handle_client(...)' - '
async def fetch_user(...)'
But you would keep synchronous helpers as regular def functions if they only format strings, validate data, or perform quick pure-Python calculations.
That separation makes code easier to reason about and avoids unnecessary async noise.
Legacy Code Guidance
If you are maintaining older asyncio code that still uses generator-based coroutines, the decorator belongs on the generator functions that were written to yield from other awaitable operations. Even there, the long-term direction is usually migration to async def.
That migration is not just syntax cleanup. Modern syntax is clearer, better supported, and easier for new contributors to understand.
Common Pitfalls
- Decorating a blocking function and assuming it is now non-blocking.
- Mixing coroutine-style functions with synchronous code without knowing where the real await points are.
- Decorating tiny helper functions that should stay ordinary
deffunctions. - Confusing historical generator-based coroutines with modern
async defcoroutines. - Keeping legacy coroutine decoration in new code when native
asyncandawaitis the clearer option.
Summary
- In modern Python, new async code should usually use
async def, not@asyncio.coroutine. - Historically, the decorator belonged on generator functions that cooperatively yielded control during async work.
- Do not decorate blocking or CPU-bound functions and expect the event loop to stay responsive.
- Keep simple synchronous helpers as normal functions.
- Think of async markers as part of the boundary where code becomes awaitable.
Related reading
- What so different about Node.js's event-driven? Can't we do that in ASP.Net's HttpAsyncHandler?
- What will happen if shared memory application works on distributed memory architecture?
- What will happen if we reorder the commands in Peterson's algorithm for mutual exclusion?
- What's a monitor in Java?
- What SOAP client libraries exist for Python, and where is the documentation for them?
- What this error means y argument is not supported when using python generator as input
- What's the best approach to asynchronous image caching on the iPhone?
- What''s the best pattern to design an asynchronous RPC application using Python, Pika and AMQP?
.png&w=3840&q=75)
Tackling System Design Interview Problems
A short course that equips you with the skills to approach system design interviews methodically.
Start the free courseNo course covers this one yet
Basis Lab writes one for you from a sentence about what you want to be able to do, then teaches it and checks you understood. Your first course is free.
Build my courseInterview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.