A Practical Intro to Async Python
When your program spends most of its time waiting, for API responses, file downloads, database replies, async Python lets it do other useful work during that wait instead of sitting idle. For anyone calling several AI or web APIs, async can turn a slow sequential script into a fast concurrent one. This lesson gives you a working mental model and the core syntax, without drowning you in theory.
What You'll Learn
- The difference between waiting (I/O-bound) and computing (CPU-bound) work
- What
async defandawaitactually do - How to run many slow calls concurrently with
asyncio.gather - When async is worth it and when it is not
The Core Idea: Don't Wait Idly
Imagine calling an AI API five times, each taking one second. Done one after another, that is five seconds, almost all of it spent waiting for the network. Async lets your program start all five, then handle each response as it arrives, finishing in close to one second.
Async helps with I/O-bound work (waiting on network, disk, APIs). It does not speed up CPU-bound work like heavy number crunching; that needs a different tool. Match the tool to the bottleneck.
Decision
Where does your program spend its time?
- If Waiting on APIs, downloads, the network
Async is a great fit (I/O-bound)
- If Heavy computation, crunching numbers
Async won't help; it's CPU-bound
Use multiprocessing or optimized libraries instead
- If One quick call, simple script
Skip async, keep it simple
The Syntax: async def and await
You define an asynchronous function with async def. Inside it, await marks a point where the function can pause and let other work run while it waits.
import asyncio
async def fetch(name):
print(f"start {name}")
await asyncio.sleep(1) # stand-in for a slow network call
print(f"done {name}")
return name
async def main():
result = await fetch("A") # await pauses here, frees the loop
print(result)
asyncio.run(main()) # runs the async program
asyncio.sleep(1) simulates a one-second wait, like an API call. The key word is await: while one coroutine waits, others can run.
Running Many Calls Concurrently
This is where async earns its keep. asyncio.gather starts several coroutines and waits for all of them together:
import asyncio
async def fetch(name):
await asyncio.sleep(1) # each "call" takes ~1 second
return f"{name} done"
async def main():
results = await asyncio.gather(
fetch("A"), fetch("B"), fetch("C"),
)
print(results)
asyncio.run(main())
# Finishes in about 1 second total, not 3
Run sequentially, three one-second calls take three seconds. With gather, they overlap their waiting and finish in about one. That is the whole payoff for API-heavy scripts.
- gather starts A, B, Call at once
- all await togetheroverlap the waiting
- results return~1s, not 3s
When Async Is and Isn't Worth It
Async adds real complexity: you can only await inside async def, and you need libraries that support it. Use it when:
- You make many independent slow I/O calls and want them concurrent.
- A long-running service must stay responsive while waiting.
Skip it when a script makes one or two calls, or when the work is CPU-heavy. For a single API call, a plain requests.get is simpler and just as fast. Reach for async when the waiting multiplies.
Try It
Run this and watch all three "calls" start before any finishes, then complete together.
You finished the course. From here, apply these skills in a project: try Build Your First AI Data App with Python or go deeper on data with Python for AI & Data Science.
Key Takeaways
- Async speeds up I/O-bound work (waiting on networks/APIs), not CPU-bound number crunching.
async defdefines a coroutine;awaitmarks a pause point where other work can run.asyncio.run(main())starts an async program;asyncio.gather(...)runs many coroutines concurrently.- Concurrent calls overlap their waiting, so many slow API calls finish in roughly the time of one.
- Async adds complexity, so use it when waiting multiplies, and keep simple scripts simple.

