Home Interview Master Python Async/Await: Top Interview Questions & Answers
Advanced 3 min · July 13, 2026

Master Python Async/Await: Top Interview Questions & Answers

Deep dive into Python async/await for interviews.

N
Naren Founder & Principal Engineer

20+ years shipping production code across the stack, with years spent interviewing engineers. Drawn from code that ran under real load.

Follow
Production
production tested
July 13, 2026
last updated
2,165
articles · all by Naren
Before you start⏱ 15-20 min read
  • Basic Python knowledge
  • Understanding of functions and decorators
  • Familiarity with I/O-bound operations
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Async/await enables concurrent code using coroutines and an event loop.
  • async def defines a coroutine; await suspends execution until the awaited coroutine completes.
  • The event loop schedules and runs coroutines cooperatively.
  • Common interview topics: event loop, tasks, futures, asyncio.gather, asyncio.create_task, and thread safety.
  • Key pitfalls: blocking the event loop, forgetting to await, mixing sync and async code.
✦ Definition~90s read
What is Python Async/Await Interview Questions?

Python async/await is a syntax for writing concurrent code using coroutines, an event loop, and non-blocking I/O.

Imagine a chef cooking multiple dishes.
Plain-English First

Imagine a chef cooking multiple dishes. Instead of waiting for a pot to boil (blocking), the chef starts the pot, then chops vegetables while it heats. Async/await lets your program do other tasks while waiting for I/O, like a chef multitasking without burning anything.

Python's async/await syntax, introduced in Python 3.5, revolutionized how we write concurrent I/O-bound code. In interviews, you'll be expected to explain not just the syntax but the underlying mechanics: the event loop, coroutines, tasks, and how to avoid common pitfalls. This guide covers the most frequently asked async/await interview questions, with detailed answers, code examples, and complexity analysis. Whether you're preparing for a senior backend role or a systems engineering position, mastering these concepts will set you apart. We'll start with the fundamentals and progress to advanced patterns like asyncio.gather, asyncio.create_task, and debugging production issues. By the end, you'll be ready to answer any async/await question with confidence.

What is an Event Loop?

The event loop is the core of every asyncio application. It runs in a single thread and manages a queue of tasks. It repeatedly checks for I/O events and runs the corresponding callbacks. In Python, you can get the current event loop with asyncio.get_event_loop(). The event loop schedules coroutines, handles callbacks, and manages subprocesses. Understanding the event loop is crucial for debugging and performance tuning. For example, if you run a blocking call like time.sleep(1), the entire event loop pauses for 1 second, blocking all other tasks. Always use asyncio.sleep() instead.

event_loop_example.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
import asyncio

async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

async def main():
    print("start")
    await say_after(1, "hello")
    await say_after(2, "world")
    print("end")

asyncio.run(main())
Output
start
hello
world
end
💡Event Loop in Production
📊 Production Insight
In high-load systems, monitor event loop latency. A delay of >100ms can cause cascading timeouts.
🎯 Key Takeaway
The event loop is a single-threaded scheduler that runs coroutines cooperatively. Never block it with synchronous I/O.

Coroutines vs Tasks vs Futures

A coroutine is a function defined with async def. When called, it returns a coroutine object. To execute it, you must either await it or schedule it as a Task. A Task is a wrapper around a coroutine that schedules it on the event loop. Tasks run concurrently. A Future is a low-level awaitable object representing a result that will be available later. In practice, you'll mostly work with coroutines and tasks. Use asyncio.create_task() to run a coroutine concurrently. Example: task = asyncio.create_task(my_coro()). Then you can await task later to get its result.

coro_task_future.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import asyncio

async def fetch_data():
    await asyncio.sleep(1)
    return "data"

async def main():
    # Coroutine object
    coro = fetch_data()
    # Task
    task = asyncio.create_task(coro)
    # Await task
    result = await task
    print(result)

asyncio.run(main())
Output
data
🔥Task vs Coroutine
📊 Production Insight
Always store references to tasks to avoid garbage collection. Use asyncio.gather for multiple tasks.
🎯 Key Takeaway
Coroutines are awaitable functions; Tasks are scheduled coroutines that run concurrently.

Using asyncio.gather and asyncio.create_task

asyncio.gather runs multiple awaitables concurrently and returns a list of results in the order they were passed. It's perfect for parallel I/O. asyncio.create_task schedules a single coroutine and returns a Task object. Use gather when you have a fixed set of coroutines; use create_task when you need to start tasks dynamically. Example: results = await asyncio.gather(coro1(), coro2(), coro3()). If any coroutine raises an exception, gather will raise it unless return_exceptions=True.

gather_example.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
import asyncio

async def fetch(url):
    await asyncio.sleep(1)
    return f"Result from {url}"

async def main():
    urls = ["url1", "url2", "url3"]
    tasks = [fetch(url) for url in urls]
    results = await asyncio.gather(*tasks)
    print(results)

asyncio.run(main())
Output
['Result from url1', 'Result from url2', 'Result from url3']
⚠ Exception Handling
📊 Production Insight
In production, limit concurrency with semaphores to avoid overwhelming external services.
🎯 Key Takeaway
Use asyncio.gather for concurrent execution of multiple coroutines; use create_task for dynamic scheduling.

Async Context Managers and Iterators

Async context managers use __aenter__ and __aexit__ methods, and are used with async with. Async iterators implement __aiter__ and __anext__, used with async for. These are essential for managing resources like database connections or streaming data. Example: async with aiohttp.ClientSession() as session:. The async with ensures proper cleanup even if an exception occurs.

async_context_manager.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import asyncio

class AsyncResource:
    async def __aenter__(self):
        print("Acquiring resource")
        await asyncio.sleep(0.1)
        return self
    async def __aexit__(self, exc_type, exc, tb):
        print("Releasing resource")
        await asyncio.sleep(0.1)

async def main():
    async with AsyncResource() as res:
        print("Using resource")

asyncio.run(main())
Output
Acquiring resource
Using resource
Releasing resource
💡Async Iterators
📊 Production Insight
Always use async with for database sessions and HTTP clients to ensure proper cleanup.
🎯 Key Takeaway
Async context managers and iterators allow you to manage resources and data streams asynchronously.

Common Pitfalls: Blocking the Event Loop and Missing Await

Two of the most common mistakes: using blocking calls (like time.sleep, requests.get) inside async functions, and forgetting to await a coroutine. Blocking calls freeze the entire event loop, destroying concurrency. Missing await returns a coroutine object instead of the result, often leading to 'coroutine was never awaited' warnings. Always use async equivalents: asyncio.sleep instead of time.sleep, aiohttp or httpx instead of requests. Use linters like flake8-async to catch these issues.

pitfall_example.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import asyncio
import time

async def blocking():
    print("Start blocking")
    time.sleep(2)  # Blocks event loop
    print("End blocking")

async def non_blocking():
    print("Start non-blocking")
    await asyncio.sleep(2)
    print("End non-blocking")

async def main():
    # This will take 4 seconds total
    await asyncio.gather(blocking(), non_blocking())

asyncio.run(main())
Output
Start blocking
Start non-blocking
End blocking
End non-blocking
⚠ Missing Await
📊 Production Insight
Use asyncio.get_event_loop().slow_callback_duration to detect blocking operations in production.
🎯 Key Takeaway
Never use blocking calls in async code; always await coroutines.

Thread Safety and Synchronization

Asyncio is single-threaded, so you don't need locks for most data structures. However, when mixing threads and asyncio (e.g., using run_in_executor), you need synchronization. Use asyncio.Lock for async code, and threading.Lock for threads. asyncio.Queue is thread-safe and can be used to communicate between threads and the event loop. Example: lock = asyncio.Lock(); async with lock:. This ensures only one coroutine accesses a shared resource at a time.

async_lock.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
import asyncio

async def worker(lock, name):
    async with lock:
        print(f"{name} acquired lock")
        await asyncio.sleep(0.1)
        print(f"{name} released lock")

async def main():
    lock = asyncio.Lock()
    await asyncio.gather(worker(lock, "A"), worker(lock, "B"))

asyncio.run(main())
Output
A acquired lock
A released lock
B acquired lock
B released lock
🔥Thread Safety
📊 Production Insight
For CPU-bound tasks, use loop.run_in_executor with a thread pool to avoid blocking the event loop.
🎯 Key Takeaway
Use asyncio.Lock for async synchronization; avoid shared mutable state between threads and the event loop.
● Production incidentPOST-MORTEMseverity: high

The Silent Database Timeout: When Async Code Blocks the Event Loop

Symptom
Users experienced intermittent 504 Gateway Timeout errors during peak hours.
Assumption
The developer assumed the new async database library was non-blocking.
Root cause
A synchronous time.sleep(1) was used inside an async function, blocking the entire event loop for one second per request.
Fix
Replaced time.sleep with asyncio.sleep and used asyncio.gather for concurrent database calls.
Key lesson
  • Never use blocking calls like time.sleep() or requests.get() inside async functions.
  • Use asyncio.sleep() and aiohttp or httpx for async HTTP.
  • Always profile async code with asyncio.run() and check for event loop blockage.
  • Use asyncio.gather or asyncio.create_task to run multiple coroutines concurrently.
  • Monitor event loop latency in production to detect blocking operations.
Production debug guideSymptom to Action4 entries
Symptom · 01
High latency or timeouts under load
Fix
Check for blocking calls (e.g., time.sleep, requests) inside async functions. Use asyncio.get_event_loop().slow_callback_duration to log slow callbacks.
Symptom · 02
Coroutines never complete (hanging)
Fix
Ensure all coroutines are awaited or scheduled as tasks. Look for missing await or forgotten create_task.
Symptom · 03
Unexpected 'coroutine was never awaited' warning
Fix
Check for async functions called without await. Use asyncio.run() properly and avoid mixing sync and async.
Symptom · 04
Race conditions or inconsistent state
Fix
Use asyncio.Lock for shared resources. Avoid relying on global variables without synchronization.
★ Quick Debug Cheat SheetCommon async/await issues and immediate fixes.
Coroutine never awaited
Immediate action
Add `await` before the coroutine call.
Commands
python -W error::RuntimeWarning script.py
grep -rn 'async def' . | grep -v 'await'
Fix now
Wrap in asyncio.run() or use await.
Blocking event loop+
Immediate action
Replace blocking calls with async equivalents.
Commands
python -m cProfile script.py
asyncio.get_event_loop().slow_callback_duration = 0.05
Fix now
Use asyncio.sleep instead of time.sleep, aiohttp instead of requests.
Task not running concurrently+
Immediate action
Use `asyncio.create_task` to schedule coroutines.
Commands
print(asyncio.all_tasks())
asyncio.get_event_loop().is_running()
Fix now
Replace sequential await calls with asyncio.gather.
Featureasyncio.gatherasyncio.create_task
PurposeRun multiple awaitables concurrentlySchedule a single coroutine
ReturnsList of results in orderTask object
Exception HandlingRaises first exception or returns exceptionsMust be awaited to get exception
Use CaseFixed set of coroutinesDynamic task creation
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
event_loop_example.pyasync def say_after(delay, what):What is an Event Loop?
coro_task_future.pyasync def fetch_data():Coroutines vs Tasks vs Futures
gather_example.pyasync def fetch(url):Using asyncio.gather and asyncio.create_task
async_context_manager.pyclass AsyncResource:Async Context Managers and Iterators
pitfall_example.pyasync def blocking():Common Pitfalls
async_lock.pyasync def worker(lock, name):Thread Safety and Synchronization

Key takeaways

1
Async/await enables concurrent I/O-bound code using a single-threaded event loop.
2
Never block the event loop with synchronous calls; always use async equivalents.
3
Use asyncio.gather and asyncio.create_task for concurrency.
4
Handle exceptions properly with return_exceptions=True or try-except.
5
Use asyncio.Lock and asyncio.Queue for synchronization.

Common mistakes to avoid

4 patterns
×

Using `time.sleep()` inside an async function

×

Forgetting to `await` a coroutine

×

Mixing synchronous and asynchronous code without a thread pool

×

Not using `asyncio.gather` for concurrent tasks

INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain the event loop in asyncio. How does it work?
Q02JUNIOR
What is the difference between a coroutine and a task?
Q03SENIOR
How would you handle exceptions in `asyncio.gather`?
Q04SENIOR
Explain how to implement a rate limiter using asyncio.
Q05JUNIOR
What is the purpose of `asyncio.run()` and how does it differ from `loop...
Q01 of 05SENIOR

Explain the event loop in asyncio. How does it work?

ANSWER
The event loop is the core scheduler that runs coroutines and handles I/O events. It runs in a single thread and uses cooperative multitasking. It maintains a queue of tasks and repeatedly checks for ready tasks, runs them until they await, then moves to the next. It uses callbacks for I/O events. In Python, you get it with asyncio.get_event_loop() and run it with asyncio.run().
FAQ · 3 QUESTIONS

Frequently Asked Questions

01
What is the difference between `asyncio.gather` and `asyncio.create_task`?
02
How do you run a synchronous function in an async application?
03
What happens if you forget to await a coroutine?
N
Naren Founder & Principal Engineer

20+ years shipping production code across the stack, with years spent interviewing engineers. Drawn from code that ran under real load.

Follow
Verified
production tested
July 13, 2026
last updated
2,165
articles · all by Naren
🔥

That's Python Interview. Mark it forged?

3 min read · try the examples if you haven't

Previous
Django Interview Questions
5 / 6 · Python Interview
Next
Flask and FastAPI Interview Questions