AtomLearnAtomLearn
DashboardGoalsPathAchievementsReviewSign In
DashboardPython CoreConcurrency Patterns
Python CoreNot Started

Concurrency Patterns

asyncio.gather vs create_task, semaphores, mixing blocking code, and asyncio vs threading vs multiprocessing

0%
Knowledge0%
Learn & Drill
Fluency0%
Drill & Speed
Retention0%
Mastery & Review
Confidence0%
All modes
Practice

Free tier: read the explanation here. Upgrade to Pro for Drills, Speed challenges & Mastery badges.

Upgrade
Knowledge
Fluency
Retention

Knowledge Debt detected

You can study this freely — but your score may plateau if these foundations have gaps. The Mastery badge requires them to be solid.

Explanation

Real interviews probe concurrency beyond basic async/await: how to limit concurrency, mix blocking calls with async code, and choose between asyncio/threading/multiprocessing.

asyncio.gather vs asyncio.create_task:

python import asyncio async def main(): # gather: schedule + wait for all, results in order results = await asyncio.gather(fetch(1), fetch(2), fetch(3)) # create_task: schedule now, await later — useful when you need # to do other work between starting and finishing task1 = asyncio.create_task(fetch(1)) task2 = asyncio.create_task(fetch(2)) result1 = await task1 result2 = await task2

Limiting concurrency with a Semaphore:

python import asyncio async def fetch_limited(sem: asyncio.Semaphore, url: str): async with sem: return await fetch(url) async def main(urls): sem = asyncio.Semaphore(5) # max 5 concurrent return await asyncio.gather(*(fetch_limited(sem, u) for u in urls)) Without a semaphore, gather()` over hundreds of URLs fires them all at once — likely to hit rate limits or overwhelm the target server.

Mixing blocking code with async — asyncio.to_thread:

python import asyncio def blocking_db_call(): return slow_sync_library.query() async def main(): result = await asyncio.to_thread(blocking_db_call) Calling a blocking function directly inside async def freezes the entire event loop — nothing else can run until it returns. asyncio.to_thread` (3.9+) runs it in a thread pool instead.

asyncio vs threading vs multiprocessing:

  • asyncio — many I/O-bound tasks, single thread, cooperative — best for thousands of concurrent network calls
  • threading — I/O-bound work with blocking libraries that don't support async — the GIL still limits CPU parallelism
  • multiprocessing — CPU-bound work — separate processes bypass the GIL entirely

Timeouts:

python try: result = await asyncio.wait_for(fetch(url), timeout=5.0) except asyncio.TimeoutError: result = None

return_exceptions in gather:

python results = await asyncio.gather(*tasks, return_exceptions=True) # results may contain Exception objects instead of raising immediately Without return_exceptions=True, the first exception propagates immediately from gather()`.

Examples

Rate-limited concurrent fetches

Semaphore caps concurrency at 10, and return_exceptions=True keeps one failed request from crashing the whole batch

import asyncio
import aiohttp

async def fetch(session, sem, url):
    async with sem:  # only N requests in flight at once
        async with session.get(url) as response:
            return await response.json()

async def fetch_all(urls, max_concurrent=10):
    sem = asyncio.Semaphore(max_concurrent)
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, sem, url) for url in urls]
        return await asyncio.gather(*tasks, return_exceptions=True)

urls = [f"https://api.example.com/items/{i}" for i in range(200)]
results = asyncio.run(fetch_all(urls))

Calling a blocking library from an async function

asyncio.to_thread offloads a blocking call so it doesn't freeze the event loop — common when wrapping legacy sync code in FastAPI

import asyncio

def slow_sync_report(report_id: int) -> dict:
    # e.g. a sync ORM call or PDF library with no async support
    time.sleep(2)
    return {"id": report_id, "status": "done"}

async def get_report(report_id: int) -> dict:
    # Runs slow_sync_report in a thread pool —
    # the event loop keeps serving other requests
    return await asyncio.to_thread(slow_sync_report, report_id)

How well did you understand this?