Published on

Python Async/Await - Write Non-Blocking Code Like a Pro

Authors

Introduction

Async programming in Python is no longer just for experts. With asyncio, async/await syntax, and modern libraries like httpx and aiofiles, you can write highly performant, non-blocking code with ease.

If you've ever noticed your Python script sitting idle while waiting for a database response or an API call, this guide is for you.

What is Asynchronous Programming?

In synchronous code, tasks run one after another. If task A takes 5 seconds, task B waits.

In asynchronous code, while task A is waiting (e.g., for a network response), Python switches to run task B. This makes your program significantly faster for I/O-bound tasks.

Synchronous:  [Task A - 5s] [Task B - 3s] = 8 seconds total
Asynchronous: [Task A waits...] + [Task B runs] = ~5 seconds total

The Basics: async and await

main.py
import asyncio

async def say_hello():
    print("Hello...")
    await asyncio.sleep(2)  # Non-blocking sleep
    print("...World!")

asyncio.run(say_hello())
  • async def defines a coroutine function
  • await suspends execution until the awaited task completes
  • asyncio.run() is the entry point to run your async code

Running Multiple Tasks Concurrently

The real power of async comes when you run multiple tasks at the same time:

main.py
import asyncio

async def fetch_data(name, delay):
    print(f"Fetching {name}...")
    await asyncio.sleep(delay)
    print(f"{name} done!")
    return f"{name} data"

async def main():
    # Run all tasks concurrently using gather
    results = await asyncio.gather(
        fetch_data("Users", 3),
        fetch_data("Products", 1),
        fetch_data("Orders", 2),
    )
    print(results)

asyncio.run(main())

Output:

Fetching Users...
Fetching Products...
Fetching Orders...
Products done!
Orders done!
Users done!
['Users data', 'Products data', 'Orders data']

All three ran concurrently! Total time: ~3 seconds instead of 6.

Making Async HTTP Requests with httpx

The most common use case for async is HTTP requests. Use httpx instead of requests:

pip install httpx
main.py
import asyncio
import httpx

async def fetch_github_user(username):
    async with httpx.AsyncClient() as client:
        response = await client.get(f"https://api.github.com/users/{username}")
        return response.json()

async def main():
    users = await asyncio.gather(
        fetch_github_user("torvalds"),
        fetch_github_user("gvanrossum"),
        fetch_github_user("yyx990803"),
    )
    for user in users:
        print(f"{user['name']} - {user['public_repos']} repos")

asyncio.run(main())

Async File I/O with aiofiles

pip install aiofiles
main.py
import asyncio
import aiofiles

async def read_file(path):
    async with aiofiles.open(path, mode='r') as f:
        content = await f.read()
    return content

async def write_file(path, content):
    async with aiofiles.open(path, mode='w') as f:
        await f.write(content)

async def main():
    await write_file("output.txt", "Hello from async file I/O!")
    content = await read_file("output.txt")
    print(content)

asyncio.run(main())

asyncio.create_task() for Background Tasks

main.py
import asyncio

async def background_job():
    await asyncio.sleep(5)
    print("Background job done!")

async def main():
    task = asyncio.create_task(background_job())

    # Do other work while the background task runs
    print("Doing other stuff...")
    await asyncio.sleep(1)
    print("Still working...")

    await task  # Wait for background task to finish

asyncio.run(main())

Common Mistakes to Avoid

1. Blocking the event loop

# ❌ BAD - blocks the event loop
import time

async def bad_function():
    time.sleep(5)  # This blocks everything!

# ✅ GOOD - non-blocking
async def good_function():
    await asyncio.sleep(5)

2. Forgetting await

# ❌ BAD - creates a coroutine but never runs it
async def main():
    result = fetch_data()  # Missing await!

# ✅ GOOD
async def main():
    result = await fetch_data()

When to Use Async (and When NOT to)

Use async for:

  • Making HTTP requests
  • Database queries (with async drivers like asyncpg, motor)
  • Reading/writing files
  • WebSockets

Don't use async for:

  • CPU-heavy tasks (use multiprocessing instead)
  • Simple scripts that don't do I/O
  • Code that uses only sync libraries

Conclusion

Async Python is one of the most powerful skills you can add to your toolkit. With asyncio, httpx, and aiofiles, writing fast, concurrent code has never been easier. Start small — convert one slow API call to async — and watch your performance improve dramatically.