What is a proper way to create awaitable function
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
In Python, the most reliable way to create an awaitable function is defining it with async def. That produces a coroutine object that integrates cleanly with asyncio. You can also build custom awaitable objects, but that path should be reserved for advanced library design.
Start with async def for Normal Application Code
async def is concise, readable, and works with await, asyncio.gather, and task cancellation. It should be your default for I O bound workflows.
This pattern is fully awaitable and easy to test. You can replace asyncio.sleep with real network or database calls later without changing the call site.
Build a Custom Awaitable with await
A custom class can be awaited when it implements __await__ and returns an iterator. This is useful for wrappers that expose a domain specific API but still run on the event loop.
__await__ should delegate to a real coroutine to keep semantics predictable. Implementing your own iterator protocol directly is possible, but it is error prone and usually unnecessary.
Wrap Blocking Functions Correctly
If you have existing synchronous code, do not call it directly inside async def when it can block for noticeable time. Use asyncio.to_thread or an executor bridge so the event loop stays responsive.
This pattern turns existing CPU or blocking library code into an awaitable boundary without freezing unrelated coroutines.
Handle Cancellation and Timeouts
Awaitable code should define timeout and cancellation behavior up front. Wrap long operations with asyncio.wait_for and catch asyncio.CancelledError only when cleanup is needed. This keeps cancellation cooperative and prevents background tasks from leaking after callers stop waiting.
Common Pitfalls
A common mistake is calling an async function without await and assuming it already ran. The call only creates a coroutine object. The event loop executes it when awaited or scheduled as a task.
Another pitfall is mixing sync and async APIs without boundaries. A blocking function inside async def can stall all concurrent tasks, which looks like random slowdowns under load.
A third issue is implementing __await__ incorrectly and returning non iterator objects. If you need custom behavior, wrap a coroutine and delegate __await__ exactly as shown earlier.
Summary
- Use
async defas the default way to create awaitable functions. - Awaitables integrate with
asynciowhen they are coroutines or define__await__. - Keep blocking code off the event loop by using
asyncio.to_thread. - Reserve custom awaitable classes for library level abstractions.
- Test async code with real event loop execution to catch scheduling bugs early.
Related reading
.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 courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.