How do you safely execute blocking code from an async function

Tests whether you know how to prevent event loop blocking by offloading sync work. Name asyncio.to_thread or run_in_executor, explain ThreadPoolExecutor scheduling, and note when processes beat threads.
What's really being asked
This question probes whether you understand that asyncio uses a single event loop per thread and that calling a blocking synchronous function directly inside an async coroutine will freeze the entire loop, stalling all concurrent tasks. It also checks if you know the specific asyncio API that bridges sync and async worlds by moving blocking work to a separate thread.
The full answer
First, explicitly state that direct calls to blocking code inside an async function block the event loop because they never yield control back to the loop. Second, name the modern function asyncio.to_thread or the underlying loop.run_in_executor mechanism. Third, explain the mechanism: the event loop submits the callable to a ThreadPoolExecutor, runs it in a worker thread, and suspends the awaiting coroutine until the result is ready, allowing other coroutines to run in the meantime. Fourth, add nuance that CPU-intensive calculations may still saturate the GIL in a thread, so a ProcessPoolExecutor can be preferable for heavy math, whereas blocking IO like a synchronous database driver is a classic use case for threads.
The mistakes people make
Calling the function directly and claiming await will help, since await only works on awaitables. Suggesting asyncio.create_task as a fix, which schedules coroutines concurrently but does not prevent blocking code from hogging the loop. Proposing to make the function async by adding the async keyword without actually using non-blocking IO underneath, which creates a fake async function that still blocks. Confusing multithreading with multiprocessing without explaining the GIL or event loop impact.
What usually comes next
When would you choose ProcessPoolExecutor over ThreadPoolExecutor? How does FastAPI handle this under the hood with dependency injection and path operations? What is the default number of worker threads in the executor and how do you configure it? Can you cancel a task running in a thread? How do you propagate contextvars into the thread?
A concrete example
Imagine a FastAPI endpoint that must use a legacy synchronous SQLAlchemy session to fetch a user. Writing user = session.get(User, user_id) inside an async def route would block the event loop for every request. Instead, you write user = await asyncio.to_thread(session.get, User, user_id). The session.get call runs in a thread from the default ThreadPoolExecutor, the endpoint coroutine yields control, and the event loop continues serving other requests until the database result returns.
Interview question
You need to call a legacy synchronous database driver inside an async FastAPI endpoint. Which approach prevents the event loop from freezing?
- a.Wrap the call with asyncio.create_task and await the resulting task
- b.await the synchronous function call directly, since await yields control to the loop
- c.Add the async keyword to the function definition and call it normally
- d.Use asyncio.to_thread to run the call in a ThreadPoolExecutor workerCorrect
Why? this is the answer
asyncio.to_thread submits the blocking call to a ThreadPoolExecutor, letting the event loop run other coroutines while the sync IO completes in a worker thread. asyncio.create_task is tempting because it enables concurrent coroutine execution, but it does not offload synchronous work to another thread, so the blocking call would still freeze the event loop.
Just read this? Test yourself on what you have been reading.
Read the original → docs.python.org
- #python
- #asyncio
- #fastapi
- #concurrency
- #event-loop
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