Debugging Python's Asyncio

Debugging asyncio is about finding what's blocking the single-threaded event loop. Use its debug mode to detect slow callbacks and run_in_executor to offload CPU-bound work. The biggest mistake is calling blocking code directly, which stalls the entire app.
Why it exists
Asyncio provides concurrency on a single thread, which is efficient but fragile. If any task blocks without yielding control, the entire event loop—and all other tasks—grinds to a halt. Debugging tools exist to find and fix these blockers that undermine the entire concurrency model.
The mental model
The asyncio event loop is like a single chef juggling many orders. The chef can only do one thing at a time. When a task hits an await, it tells the chef, "I'm waiting for the oven, go work on another dish." If a task starts a long, blocking operation (like chopping vegetables for 10 minutes) without await, the chef is stuck on that one task, ignoring all other orders. Debugging is about identifying these "stuck" tasks.
How it works
Python's asyncio has a built-in debug mode. Enable it by passing debug=True to asyncio.run() or setting the PYTHONASYNCIODEBUG=1 environment variable. When active, it provides critical diagnostics. First, it logs callbacks that take longer than a configured threshold (default 100ms). Second, it raises exceptions if you call non-threadsafe asyncio functions from the wrong thread. Third, it can log how long I/O operations take, helping you spot unexpected delays.
When to use it
Use asyncio's debug mode during development to catch performance issues early. It's invaluable for identifying slow, synchronous code masquerading as async. For handling blocking code you can't make async (like a CPU-intensive library), use loop.run_in_executor(). This moves the blocking call to a separate thread pool, freeing the event loop to continue processing other tasks. For interacting with the event loop from another thread, use loop.call_soon_threadsafe().
When not to use it
Debug mode adds overhead and should not be enabled in production environments, as the performance cost can be significant. Also, do not use run_in_executor for code that is already non-blocking and awaitable; that just adds unnecessary thread-shuffling overhead. The goal is to isolate truly blocking code, not wrap everything.
One canonical example
A common mistake is calling a blocking function like time.sleep() instead of await asyncio.sleep(). The first blocks the entire event loop, while the second correctly pauses only the current task.
To fix a truly blocking, CPU-bound function cpu_heavy_work(), you would run it in an executor:
loop = asyncio.get_running_loop()
result = await loop.run_in_executor(None, cpu_heavy_work, arg1, arg2)This prevents the cpu_heavy_work function from stalling other concurrent tasks.
Interview question
Which scenario best justifies using loop.run_in_executor() in an asyncio application?
- a.To enable detailed logging of callback execution times during development.
- b.When needing to safely interact with the event loop from an external thread.
- c.To prevent a CPU-bound, synchronous task from stalling the event loop.Correct
- d.When an awaitable function is performing slower than expected.
Why? this is the answer
loop.run_in_executor() is designed to offload CPU-intensive, synchronous (blocking) tasks to a separate thread pool, preventing them from blocking the single-threaded event loop. Using it for an already awaitable function would add unnecessary overhead, not improve performance.
Just read this? Test yourself on what you have been reading.
Read the original → docs.python.org
You just looked this up. Could you explain it out loud?
That is the part interviews actually test. Tezvyn takes questions like this one and gives you what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.
The iPhone app is on the way
We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.
Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.
We are hiring for this. Open roles that interview on python — each one lists the topics its interview covers.
See open roles