InfoWok
Beginner

Async and Await in Python: A Primer for AI Agent Code (2026)

An async and await in Python primer for reading AI agent code: what async def, await, and asyncio.run mean, and the two mistakes beginners always hit.

SK
Sukhveer Kaur
Published June 22, 2026
5 min read
Async and await in Python primer banner showing an async def function with an await call and an asyncio event loop on a dark backgroundAI Engineering
ASYNC & AWAIT
On this page +

Open almost any AI agent example and the first line of real code is async def. Then every interesting call has an await in front of it, and the whole thing ends with asyncio.run(main()). If that syntax makes a simple tutorial feel advanced, you are not missing anything deep — you are missing two keywords that nobody stopped to explain. This async and await in Python primer explains them, so agent code reads like plain English.

You do not need to master async to build agents in 2026. You need to read it, because the LLM SDKs and frameworks you will use ship async-first examples. By the end of this short read you will know what async def and await mean, when they help, and the two mistakes that catch every beginner.

🟢 Beginner⏱️ 12 min readStack: Python 3.10+ (reading level — no agent code needed)
Before you start
  • You can read a basic Python function and run a .py file — new to that? The Python for AI agents primer covers the basics
  • That’s the whole list — this primer assumes no async experience at all
🎯 Key takeaways
  • async def makes a coroutine: calling it doesn’t run the body, it hands you a coroutine you have to await.
  • await means “pause here until this finishes” and lets the event loop run other work while you wait — that’s concurrency, not parallelism.
  • asyncio.run(main()) is the on-ramp: it starts the event loop and runs your top-level async function.
  • The two beginner traps are forgetting await (nothing runs) and calling slow blocking code inside async (everything freezes).

What async and await actually mean: waiting without freezing#

Picture a barista taking an order, starting the espresso machine, and — instead of standing there watching it drip — taking the next customer’s order while the machine works. Same person, one counter, but no idle waiting. That is async: one thread that switches to other work whenever it would otherwise be stuck waiting.

The thing it waits on is almost always I/O (input/output) — a network request, a database query, an LLM API call. These are slow not because your computer is busy, but because it’s waiting on someone else’s server. Async lets a single program fire off one API call and start another while the first is still in flight. Async is about not wasting time waiting, not about doing two calculations at once. That second thing is parallelism, and it’s a different tool (threads and processes).

This is exactly why agent code is async-first: an agent’s whole life is waiting on model and tool calls over the network. Async is the natural fit.

async def and await: the only two keywords#

Here are the two pieces of syntax you’ll read constantly. A function defined with async def is a coroutine. The surprise for newcomers: calling it does not run the body.

python
import asyncio
 
async def get_weather(city: str) -> str:
    await asyncio.sleep(1)          # pretend this is a slow API call
    return f"It's sunny in {city}."
 
result = get_weather("Pune")        # ⚠️ does NOT run — result is a coroutine object

To actually run a coroutine, you await it from inside another async function. await means “run this, and pause me until it returns — and while I’m paused, let other work happen.”

python
async def main():
    report = await get_weather("Pune")   # runs it, waits for the result
    print(report)
🔑 The one-sentence version

async def defines work that can be paused; await is where it pauses and resumes. Every other piece of asyncio is built on those two ideas.

Running async code: asyncio.run#

You can only await inside an async function — so how does the first one start? That’s what asyncio.run() is for. It starts the event loop (the scheduler that runs coroutines and switches between them) and runs your top-level coroutine to completion.

python
import asyncio
 
async def main():
    print(await get_weather("Delhi"))
 
asyncio.run(main())     # the single entry point that boots the loop

The rule of thumb: asyncio.run(main()) appears exactly once, at the very bottom of the script. Everything async flows out of that one call.

Doing things at once: gather and TaskGroup#

The real payoff comes when you await several slow things together instead of one after another. The modern, safe way is asyncio.TaskGroup (Python 3.11+), which starts tasks and waits for all of them — and cancels the rest if one fails.

python
async def main():
    async with asyncio.TaskGroup() as tg:
        t1 = tg.create_task(get_weather("Pune"))
        t2 = tg.create_task(get_weather("Delhi"))
    print(t1.result(), t2.result())   # both ran concurrently

Run two one-second calls sequentially and it takes two seconds; run them in a TaskGroup and it takes about one. For an agent calling several tools at once, that difference is the whole point.

💡 TaskGroup over gather in 2026

Older tutorials use asyncio.gather(...). It still works, but TaskGroup has cleaner error handling — if one task fails, it cancels the others instead of leaving them running. Prefer it for new code.

The two mistakes that bite beginners#

Almost every async bug a beginner hits is one of these two, so learn to spot them now.

Forgetting await. Call a coroutine without await and the body never runs — you get a coroutine object and a “coroutine was never awaited” warning, with no error and no result. If an async call seems to do nothing, check for a missing await before anything else.

Blocking the loop. The event loop only switches at an await. Drop a slow synchronous call into an async function — time.sleep(5), requests.get(...), a heavy CPU loop — and the whole program freezes, because nothing yields control back to the loop.

⚠️ Use the async version of slow calls

Inside async code, use await asyncio.sleep() not time.sleep(), and an async HTTP client like httpx.AsyncClient not requests. A blocking call in a coroutine defeats the entire purpose of async — one frozen call stalls every other task.

When you don’t actually need async#

Async is not free — it adds keywords, an event loop, and a class of bugs. If your script makes one API call and exits, plain synchronous code is simpler and just as fast. Reach for async when you’re doing many I/O operations and waiting is the bottleneck: several tool calls at once, a web server handling many requests, a batch of documents to fetch. That’s why agent frameworks default to it — but a small one-shot script doesn’t need it.

Quick recap#

Everything above, in five lines:

  • async def defines a coroutine; calling it returns an object you must await.
  • await runs a coroutine and pauses until it’s done, freeing the loop for other work.
  • asyncio.run(main()) boots the event loop — once, at the bottom of the script.
  • TaskGroup runs several coroutines concurrently and fails safely.
  • Watch for a missing await (nothing runs) and blocking calls (everything freezes).

Frequently Asked Questions#

What do async and await actually do? async def makes a coroutine that doesn’t run until awaited; await runs it and pauses the caller until it returns, letting the event loop do other work meanwhile.

Do I need async to build AI agents? You need to read it, because frameworks ship async examples. Writing your own comes later, when you actually need concurrency.

Async or threads? Async (single thread, switches at await) suits I/O-bound work like API calls; threads suit CPU-bound work. Agents are I/O-bound, so async fits.

Why doesn’t my coroutine run? You called it without await. Add await inside an async function, or run the top one with asyncio.run().

Conclusion#

Async stops being intimidating the moment you read async and await as one idea: async def marks work that can pause, await is where it pauses and resumes, and asyncio.run starts the whole thing. That’s enough to follow any agent quickstart line by line — which is the real goal, since you’ll read far more async code than you write at first.

What’s the first piece of async agent code you want to actually understand — an LLM call, a tool, a whole agent loop? Tell me in the comments.

🧭 Where to go from here

Frequently asked questions

What do async and await actually do in Python? +
`async def` marks a function as a coroutine — calling it returns a coroutine object instead of running the body. `await` runs that coroutine and pauses the current function until it finishes, freeing the event loop to run other work in the meantime. Together they let one thread handle many slow I/O operations without blocking.
Do I need to learn async before building AI agents? +
Learn to read it first, not write it. Most LLM SDKs and agent frameworks ship async examples, so you will see `async def` and `await` everywhere. Understanding what the two keywords mean is enough to follow the code; you can author your own async functions later, when a project actually needs concurrency.
What is the difference between async and threads? +
Async runs on a single thread and switches between tasks at each `await`, so it shines for I/O-bound work like API calls. Threads (and processes) run things in parallel and suit CPU-bound work. For AI agents, which spend their time waiting on network calls, async is usually the better and lighter fit.
Why does my coroutine not run? +
You almost certainly called it without `await`. Writing `get_data()` on an async function just creates a coroutine object and a 'coroutine was never awaited' warning. Add `await get_data()` inside an async function, or run the top-level one with `asyncio.run(main())`.

References

  1. Coroutines and Tasks — Python documentation (asyncio)
  2. asyncio — Asynchronous I/O (Python docs)
  3. PEP 492 — Coroutines with async and await syntax
  4. asyncio.TaskGroup — Python documentation
Written by
Sukhveer Kaur
Sukhveer KaurSoftware Developer & AI Engineer

Sukhveer is a software developer specialising in AI systems and backend engineering. She has hands-on experience designing agentic AI applications, working with large language model pipelines, autonomous agent frameworks, and cloud-native services in Java and Python. At InfoWok, she bridges the gap between cutting-edge AI research and practical implementation — helping developers understand and apply emerging technologies through clear, experience-backed writing.

New AI engineering guides, the day they ship

Real Python, production depth. No digest spam.

Comments