Skip to content
LakeBench
ProblemsCommunityPricing
Sign inStart practicing
Back
  1. Home
  2. Interview prep
  3. asyncio for I/O-bound ingestion

Python · Language Internals Interviewers Still Ask

asyncio for I/O-bound ingestion

Hardpython-67
asyncioconcurrencyio-boundhttpxsemaphore

Question

When would you use asyncio in a data pipeline, and how is it different from threading?

Solution

Use asyncio when the job spends most of its time waiting on the network, for example making thousands of HTTP calls, and you want many of them in flight at once. It runs everything in one thread, switching between tasks whenever one is waiting. Threads do the same job by running in separate OS threads.

How it works

An event loop runs coroutines (functions defined with async def). When a coroutine reaches an await on something slow, such as a network response, it hands control back to the loop, which runs another coroutine. While one request waits, hundreds of others can make progress, with no thread per request.

import asyncio, httpx

async def fetch(client, sem, url):
    async with sem:                          # limit concurrency
        r = await client.get(url, timeout=30)
        r.raise_for_status()
        return r.json()

async def main(urls):
    sem = asyncio.Semaphore(20)              # at most 20 requests at a time
    async with httpx.AsyncClient() as client:
        return await asyncio.gather(*(fetch(client, sem, u) for u in urls))

results = asyncio.run(main(urls))

Always limit concurrency

Starting 10,000 requests at once can overload the other side, trigger rate limits, or exhaust file handles. A semaphore (or a bounded queue of workers) keeps a sensible number in flight.

The big pitfall: blocking calls

Inside an async def, a normal blocking call such as requests.get() or time.sleep() freezes the whole event loop, and every other task stops until it returns. Use async libraries (aiohttp, httpx.AsyncClient, asyncpg), or push blocking work to a thread with await loop.run_in_executor(None, blocking_function) (or asyncio.to_thread).

asyncio versus threads versus processes

  • asyncio: many concurrent I/O tasks, low overhead, needs async-aware libraries, and code that is "async all the way".
  • Threads: simpler when you use blocking libraries. Fine for tens or a few hundreds of concurrent I/O calls.
  • Processes: for CPU-bound work, since the GIL prevents threads from running Python code in parallel. asyncio does not help CPU-bound work at all.

How to answer

Say it fits "lots of waiting" workloads such as API ingestion, always bound the concurrency, and never block the loop. Then say that for a handful of downloads a thread pool is simpler, and often enough.

🎯 Put this concept into practice

Solidify this answer with real hands-on interview drills in the browser studio.

Open related drill →
PreviousNext