Skip to main content

Sync vs Async: The Wait Problem

intermediate13 min readLesson 97 of 169

Concurrency vs parallelism, and why one thread can juggle thousands of waits.

Most of a network-bound program's life is waiting โ€” for responses, disks, databases. Synchronous code waits blocked: nothing else happens.

# SYNC: total time = sum of all waits
def fetch_all(urls):
    results = []
    for url in urls:
        results.append(fetch(url))     # blocks ~200ms each
    return results

Concurrency is handling many waits during the same period (one worker switching between them). Parallelism is doing many things at the same instant (many workers). Async Python gives you concurrency on one thread:

import asyncio

async def fetch(url):
    await asyncio.sleep(0.2)           # stand-in for a network wait
    return url

async def fetch_all(urls):
    return await asyncio.gather(*(fetch(u) for u in urls))

async def defines a coroutine โ€” a function that can pause (await) and let the event loop run someone else while it waits. When the wait finishes, the loop resumes it. Ten 200ms waits overlap into ~200ms total, not 2 seconds.

When async is (and isn't) the answer

Async shines for I/O-bound work: APIs, sockets, databases. It does nothing for CPU-bound work (image processing, math) โ€” that needs processes. And the rule that saves beginners: you cannot await inside a regular def, and you cannot call blocking functions inside async code without freezing the loop. asyncio.run(main()) is the door into async-land.

Now practice

Coroutine DrillsWrite async functions and run them with asyncio.run.2 challenges ยท ยท ~25 min