Interview frequency: …
Short answer
Pass it to asyncio.to_thread (or loop.run_in_executor) so it runs in a separate thread without blocking the event loop, and use a ProcessPoolExecutor with run_in_executor for CPU-bound work.
Explanation
to_thread suits blocking I/O libraries such as an old SDK or reading large files. It improves responsiveness, but it does not make Python computation parallel on traditional CPython because of the GIL. to_thread uses the default executor, which has a limited number of threads, so thousands of blocking calls will queue behind it; set an appropriate pool size or look for an asynchronous alternative.
Code example
import asyncio
from concurrent.futures import ProcessPoolExecutor
async def handler(path: str):
data = await asyncio.to_thread(read_large_file, path) # I/O Blocking
loop = asyncio.get_running_loop()
with ProcessPoolExecutor() as pool:
digest = await loop.run_in_executor(pool, heavy_hash, data)
return digestCommon mistakes
- Calling a blocking function directly inside a coroutine, which delays all other requests.
- Using to_thread for heavy computation and expecting real parallelism.
Follow-up questions
- What is an event loop and what is the role of a coroutine in asyncio?
- What is the effect of the GIL in CPython, and what changed with the free-threaded build?