PYTHON

Python asyncio Module: Syntax, Usage, and Examples

The asyncio module lets you run asynchronous code in Python, so your program can handle many tasks without blocking on slow operations. It’s especially useful for network requests, real-time apps, and other I/O-heavy work.


How to Use the asyncio module in Python

The module is built around three core ideas:

  • Coroutines: functions defined with async def
  • Awaiting: pausing a coroutine with await until something finishes
  • Event loop: the engine that schedules tasks and runs them

Here’s the smallest example that runs an async function:

import asyncio

asyncdefsay_hi():
print("Hi from async code!")

asyncio.run(say_hi())

Basic coroutine syntax

A coroutine is just a function that uses async def. Inside it, you can use await when you call other async functions.

import asyncio

asyncdeffetch_profile():
await asyncio.sleep(1)
return {"name":"Mina","status":"online"}

asyncdefmain():
    profile =await fetch_profile()
print(profile)

asyncio.run(main())

asyncio.sleep() is the async version of time.sleep(). The difference is huge: time.sleep() blocks the whole program, while asyncio.sleep() gives control back to the event loop so other tasks can run.

Running multiple tasks at once

To run coroutines concurrently, create tasks and wait for them together.

import asyncio

asyncdefdownload_file(name, seconds):
print(f"Starting {name}")
await asyncio.sleep(seconds)
print(f"Finished {name}")

asyncdefmain():
    task1 = asyncio.create_task(download_file("report.pdf",2))
    task2 = asyncio.create_task(download_file("photo.png",1))

await task1
await task2

asyncio.run(main())

This is already better than running them one by one. Both tasks start, and the faster one finishes first.

Using gather() for cleaner concurrency

asyncio.gather() runs many coroutines concurrently and collects the results.

import asyncio

asyncdefget_score(player):
await asyncio.sleep(1)
returnf"{player}: 100"

asyncdefmain():
    results =await asyncio.gather(
        get_score("Amina"),
        get_score("Kai"),
        get_score("Noah")
    )
print(results)

asyncio.run(main())


When to Use the asyncio module in Python

Async code shines when your program spends time waiting. Think “waiting for the outside world”.

1) Making lots of network requests

Calling APIs and loading data from the internet can be slow. With async code, you can start many requests and process them as results arrive.

That’s perfect for:

  • web scraping (responsibly)
  • fetching user profiles from a service
  • checking multiple endpoints

2) Building real-time apps

Async fits naturally in apps that stay “alive” and react to events.

Examples include:

  • chat servers
  • multiplayer game logic
  • real-time dashboards
  • bots that listen for messages

3) Handling many connections at the same time

If a server needs to handle hundreds or thousands of clients, blocking code becomes a problem quickly. Async makes it possible to keep things responsive even under load.

4) Running background I/O while doing other work

A program can continue running while waiting for:

  • file reads
  • database operations (through async libraries)
  • incoming messages
  • sensor updates

Examples of the asyncio module in Python

Below are three common patterns that show how async code behaves in practice.

Example 1: A simple timer that doesn’t block

import asyncio

asyncdeftick():
for iinrange(3):
print(f"Tick {i + 1}")
await asyncio.sleep(1)

asyncio.run(tick())

This prints one tick per second, but it still leaves room for other async tasks to run.


Example 2: Running several “slow” tasks together

Let’s simulate multiple slow operations, like downloading data from different services.

import asyncio

asyncdeffetch_data(source, seconds):
print(f"Fetching from {source}...")
await asyncio.sleep(seconds)
returnf"Data from {source}"

asyncdefmain():
    results =await asyncio.gather(
        fetch_data("Service A",2),
        fetch_data("Service B",1),
        fetch_data("Service C",3)
    )

for itemin results:
print(item)

asyncio.run(main())

Even though one task takes 3 seconds, the total time is close to 3 seconds, not 6. That’s the main win.


Example 3: A repeating task using an infinite loop

Some apps need to keep checking for updates.

import asyncio

asyncdefmonitor_status():
whileTrue:
print("Checking status...")
await asyncio.sleep(2)

asyncdefmain():
    task = asyncio.create_task(monitor_status())
await asyncio.sleep(6)
    task.cancel()
print("Stopped monitoring.")

asyncio.run(main())

This shows how a background task can run repeatedly until you stop it.


Learn More About the asyncio module in Python

Once you get past the basics, a few concepts come up again and again.

Await vs create_task()

await means “pause until this finishes”.

create_task() means “start this now, but don’t wait yet”.

Compare the two approaches.

Waiting one by one

import asyncio

asyncdefjob(name):
await asyncio.sleep(1)
return name

asyncdefmain():
    a =await job("A")
    b =await job("B")
print(a, b)

asyncio.run(main())

This runs job A, then job B.

Running both at the same time

import asyncio

asyncdefjob(name):
await asyncio.sleep(1)
return name

asyncdefmain():
    task_a = asyncio.create_task(job("A"))
    task_b = asyncio.create_task(job("B"))

    results =await asyncio.gather(task_a, task_b)
print(results)

asyncio.run(main())

This runs them concurrently.


The event loop in plain language

The event loop is like a very organized manager.

  • It starts tasks
  • pauses tasks that are waiting
  • resumes tasks when they’re ready
  • keeps the program moving

You usually don’t manage the loop directly in beginner code because asyncio.run() does it for you.


Common mistakes with async code

A few things trip people up early.

Mistake 1: Forgetting to await a coroutine

This creates a coroutine object but doesn’t run it.

import asyncio

asyncdefgreet():
print("Hello!")

asyncdefmain():
    greet()# Missing await

asyncio.run(main())

Fix:

import asyncio

asyncdefgreet():
print("Hello!")

asyncdefmain():
await greet()

asyncio.run(main())

Mistake 2: Using blocking code inside async functions

Calling time.sleep() inside a coroutine blocks the event loop.

Bad:

import asyncio
import time

asyncdefslow():
    time.sleep(2)
print("Done")

asyncio.run(slow())

Better:

import asyncio

asyncdefslow():
await asyncio.sleep(2)
print("Done")

asyncio.run(slow())


Handling errors in async tasks

If multiple tasks run at once, one might fail. asyncio.gather() can raise an exception when that happens.

import asyncio

asyncdefrisky_job():
await asyncio.sleep(1)
raise ValueError("Something went wrong")

asyncdefsafe_job():
await asyncio.sleep(1)
return"All good"

asyncdefmain():
try:
        results =await asyncio.gather(risky_job(), safe_job())
print(results)
except ValueErroras error:
print("Caught error:", error)

asyncio.run(main())

In real apps, logging these errors matters, especially when tasks run in the background.


Timeouts with wait_for()

If you don’t want to wait forever, wrap a coroutine in asyncio.wait_for().

import asyncio

asyncdefslow_request():
await asyncio.sleep(5)
return"Response"

asyncdefmain():
try:
        result =await asyncio.wait_for(slow_request(), timeout=2)
print(result)
except asyncio.TimeoutError:
print("Request timed out")

asyncio.run(main())

Timeouts are common in network code.


asyncio vs threading

Both can help with concurrency, but they shine in different situations.

  • Async is great for I/O tasks like network calls and waiting for input.
  • Threads can help when you need blocking libraries or background work that doesn’t support async.

CPU-heavy tasks, like video encoding or large math operations, usually need multiprocessing or optimized libraries, because async won’t make CPU work faster.


Summary

The asyncio module helps you write non-blocking code in Python using async def, await, and an event loop. Use it for I/O-heavy tasks like network requests, real-time applications, and handling many connections at once. Once you understand coroutines, tasks, and tools like gather() and wait_for(), async programming starts to feel natural and can seriously speed up your workflow without adding messy complexity.