Python

P
PythonHub
Progress 0%

Python

  • Home
  • History of Python
  • Applications of Python
  • Introduction To Python
    • What is Python
    • Character Set
    • Tokens in Python
    • Python Execution Mode
    • Variable And Identifiers
    • Data Types in Python
    • Operators And Expressions
    • Constants in Python
    • Assignment Statement
    • Input / Output in Python
    • Simple Python Scripts
    • Namespace in Python
    • πŸ“ Assignments
  • Operators in Python
    • Arithmetic Operators
    • Assignment Operators
    • Shorthand Assignment Operators
    • Relational Operators
    • Logical Operators
    • Bitwise Operators
    • Special Operators
    • πŸ“ Assignments
  • Input Output in Python
    • Accept Input
    • Output Formatting
    • πŸ“ Assignments
  • Conditional Statement
    • Decision Making
    • if Statement
    • IF-ELSE STATEMENT
    • IF-ELSE LADDER
    • NESTED IF-ELSE
    • Short Hand IF-ELSE
    • πŸ“ Assignments
  • Loops
    • Introduction to Loops
    • While Loop
    • Nested While Loop
    • πŸ“ While Loop Assignments
    • For Loop
    • For Loop Examples
    • Nested For Loop
    • Nested For Loop Examples
    • Infinite While Loops
    • Infinite For Loops
    • Break, Continue and Else in Loops
    • Difference Between For and While Loop
    • For Each Loop
    • πŸ“ For Each Assignments
    • πŸ“ All Loops Assignments
  • List
    • List in Python
    • Access List Elements
    • List Functions
    • Iterate (Loop) List
    • List Comprehension
    • πŸ“ Assignments
  • Tuple
    • Tuple in Python
    • Access Tuple Elements
    • Tuple Functions
    • Iterate (Loop) Tuple
    • Unpack Tuple
    • Tuple Comprehension
    • πŸ“ Assignments
  • Set
    • Set in Python
    • Access Set Elements
    • Set Methods
    • Iterate (Loop) Set
    • Pack/Unpack Set
    • Set Comprehension
    • πŸ“ Assignments
  • Dictionary
    • Dictionary
    • Access Dictionary Items
    • Dictionary Methods
    • Iterate (Loop) Dictionary
    • Formatting Dictionaries
    • Nested Dictionaries
    • Dictionary Comprehension
    • πŸ“ Assignments
  • Diff List Tuple Set Dictionary
    • List vs Tuple
    • List vs Set
    • List vs Dictionary
    • Tuple vs Set
    • Tuple vs Dictionary
    • Dictionary vs Set
    • πŸ“ Assignments
  • Exception
    • Error vs Exception
    • Types of Exception
    • Exception Handling
    • User Defined Exception
    • Logging Exception
    • πŸ“ Assignments
  • Functions
    • Introduction to Functions
    • Modular Programming
    • Types of Functions
    • Inbuilt Functions
    • Need For User-Defined Function
    • Elements of User Defined Function
    • Function Arguments
    • Nesting of Functions
    • Recursion
    • Global Local and Non Local
    • Python Lambda Functions
    • πŸ“ Assignments
  • Python Module
    • Introduction to Module
    • Inbuilt Modules in Python
    • User Defined Module
    • πŸ“ Assignments
  • File Handling
    • Introduction to Files
    • Create File
    • Read Files
    • Write to File
    • Rename File
    • Copy File
    • Move File
    • List Files in Directory
    • Binary Files
    • Zipping and Unzipping Files
    • πŸ“ Assignments
  • Strings
    • Basics of Strings
    • String Special Operators
    • String Formatting Operators
    • String Methods
    • πŸ“ Assignments
  • Regular Expressions
  • Python OOPS
    • Basics of Object Oriented
    • What are Classes and Objects?
    • Creating Class and Object
    • OOP vs Procedural Programming
    • Difference Between Classes and Objects
    • Constructors
    • Destructor
    • Built Class Methods and Attributes
    • Class and Instance Variables
    • Inheritance in Python
    • Single Inheritance
    • Multiple Inheritance
    • Multilevel Inheritance
    • Hierarchical Inheritance
    • Hybrid Inheritance
    • Abstraction
    • Method Overriding
    • Abstract Method
    • Interfaces in Python
    • Abstract Class vs Interface
    • Public, Private and Protected
    • Overloading vs Overriding
    • Inheritance vs Composition
    • Encapsulation
    • Polymorphism
    • Inner Classes
    • πŸ“ Assignments
  • Advanced Python
    • Iterator in Python
    • Generator in Python
    • Decorator in Python
    • Type Hints in Python
    • Async/Await Programming
    • Dataclasses in Python
    • Context Managers in Python
    • Property Decorator in Python
    • __slots__ in Python
    • Enums in Python
    • Walrus Operator in Python
    • Match-Case in Python
    • πŸ“ Assignments
  • Python Standard Library
    • Collections Module
    • Itertools Module
    • Functools Module
    • Datetime Module
    • JSON Module
    • OS Module
    • Sys Module
    • Random Module
    • Math Module
    • πŸ“ Assignments
  • Python Testing
    • Unit Testing in Python
    • Pytest Framework
    • Mocking in Python
    • πŸ“ Assignments
  • Python Best Practices
    • PEP 8 Style Guide
    • Docstrings in Python
    • Logging in Python
    • Code Optimization Tips
    • Debugging Techniques
    • πŸ“ Assignments
  • MySQL Database in Python
    • Introduction to MySQL with Python
    • DBMS vs File System
    • Connecting to MySQL Database
    • Create Database in MySQL
    • Create Table in MySQL
    • Insert Data in MySQL
    • Insert Multiple Rows
    • Select Data from MySQL
    • WHERE Clause in MySQL
    • Update Data in MySQL
    • Delete Data from MySQL
    • Parameterized Queries
    • Transaction Management
    • Error Handling
    • Connection Pooling
    • MySQL Drivers Guide
    • Joins in MySQL
    • Aggregation Functions
    • Backup MySQL Database
    • Best Practices
    • πŸ“ Assignments
  • MySQL Database Operations
    • SELECT Statement
    • MySQL Operators
    • DDL Statements
    • DML Statements
    • Subqueries
    • JOIN Operations
    • Aggregation
    • Case Study
    • πŸ“ Assignments
  • Graphics in Python
  • Threads in Python
    • Introduction to Threads
    • Process vs Threads
    • Concurrent Programming & GIL
    • Uses of Threads
    • Creating Threads
    • Single Tasking
    • Multi Tasking
    • Thread Synchronization
    • πŸ“ Assignments
  • Interview Questions & Answers
  • Python Case Studies
  • Multiple Choice Questions
  • πŸ“ Practice Problems
Get in Touch
  • tech2dsm@gmail.com

© Sankalan Data Tech

Python Language Interactive Tutorial

Python: Async/Await

Python Async/Await - Complete Guide

Learn how to write concurrent code that runs faster.

Created by Sankalan Data Tech Team Verified
Data Engineers, Analysts, Scientists & Trainers
Created by experienced Python developers, data engineers, and data scientists to make programming easy through practical examples, real-world experience, and clear explanations.
On this page:
  • What is Async/Await?
  • Sync vs Async
  • Using async and await
  • How to Run Async Code
  • Running Multiple Tasks
  • Real-World Example
  • Best Practices
  • Try It Yourself
  • Quick Quiz
  • Frequently Asked Questions
Share this tutorial:
Twitter LinkedIn Facebook WhatsApp Reddit Telegram Email Copy Link
What You'll Learn Here
  • What is async/await β€” writing code that can run multiple things at once
  • Sync vs Async β€” understanding the difference
  • How to use async/await β€” the basic syntax
  • Running async code β€” using asyncio
  • Multiple tasks β€” running many tasks together
  • Real-world use β€” web requests and more

What is Async/Await?

Async/await is a way to write code that can do multiple things at the same time without waiting for each thing to finish before starting the next.

Think of it like cooking dinner. Instead of chopping all the vegetables, then boiling the water, then cooking the pasta one after another (synchronous), you can start the water boiling, while that's happening, chop the vegetables, while that's happening, start cooking the sauce (asynchronous).

In Python, async and await help you write this kind of code. They let you pause a function and let other functions run while you wait.

πŸ’‘ Key concept: Async code doesn't run multiple things at the exact same time. It runs one thing, then pauses and runs another, switching back and forth.

Sync vs Async

1

Understanding the Difference

Let's see the difference between synchronous and asynchronous code with a simple example.

# Sync vs Async Code

import time
import asyncio

print("=" * 50)
print("SYNC vs ASYNC CODE")
print("=" * 50)

# ============================================================
# SYNCHRONOUS CODE (One at a time)
# ============================================================

print("\n1. SYNCHRONOUS CODE")

def sync_task(name, delay):
    """Simulate a task that takes time"""
    print(f"   Starting {name}")
    time.sleep(delay)  # Blocking β€” nothing else can happen
    print(f"   Finished {name}")
    return f"{name} done"

def sync_main():
    """Run tasks one at a time"""
    print("   Running synchronous tasks...")
    
    # Each task waits for the previous one to finish
    result1 = sync_task("Task 1", 2)
    result2 = sync_task("Task 2", 2)
    result3 = sync_task("Task 3", 2)
    
    print(f"   All done! Results: {result1}, {result2}, {result3}")

start = time.time()
sync_main()
print(f"   Total time: {time.time() - start:.2f} seconds")


# ============================================================
# ASYNCHRONOUS CODE (Switch between tasks)
# ============================================================

print("\n2. ASYNCHRONOUS CODE")

async def async_task(name, delay):
    """Simulate a task that takes time (non-blocking)"""
    print(f"   Starting {name}")
    await asyncio.sleep(delay)  # Non-blocking β€” other tasks can run
    print(f"   Finished {name}")
    return f"{name} done"

async def async_main():
    """Run tasks concurrently"""
    print("   Running asynchronous tasks...")
    
    # Start all tasks at once
    task1 = async_task("Task 1", 2)
    task2 = async_task("Task 2", 2)
    task3 = async_task("Task 3", 2)
    
    # Wait for all to complete
    results = await asyncio.gather(task1, task2, task3)
    
    print(f"   All done! Results: {', '.join(results)}")

# Run the async code
start = time.time()
asyncio.run(async_main())
print(f"   Total time: {time.time() - start:.2f} seconds")


# ============================================================
# THE DIFFERENCE
# ============================================================

print("\n" + "-" * 30)
print("THE DIFFERENCE")
print("-" * 30)
print("""
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ SYNCHRONOUS                   ASYNCHRONOUS                  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Runs one thing at a time     Can switch between tasks      β”‚
β”‚ Waits for each task to       Starts tasks, then waits      β”‚
β”‚ complete before starting     for all to complete           β”‚
β”‚ the next                     together                       β”‚
β”‚                                                             β”‚
β”‚ Total time: 6 seconds        Total time: 2 seconds          β”‚
β”‚ (3 tasks Γ— 2 seconds)        (all tasks run together)       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

βœ… Async code can save time when tasks are waiting for
   external things (network requests, file I/O, etc.)
""")

Sync vs async key points:

  • Synchronous β€” runs one thing at a time, waits for each to finish
  • Asynchronous β€” starts tasks, switches between them while waiting
  • Better with I/O β€” async helps with network, file operations
  • Not always faster β€” async adds overhead for simple tasks

Quick Check: What's the main difference between sync and async code? (Answer: Sync runs one at a time; async can switch between tasks while waiting)

Using async and await

2

The Basic Syntax

Using async/await is simple. You just need to know two keywords:

  • async def β€” defines a function that can be paused
  • await β€” pauses the function until something is ready
# async and await Basics

import asyncio

print("=" * 50)
print("ASYNC AND AWAIT BASICS")
print("=" * 50)

# ============================================================
# CREATING AN ASYNC FUNCTION
# ============================================================

print("\n1. CREATING ASYNC FUNCTIONS")

async def simple_async():
    """A simple async function"""
    print("   Starting async function")
    await asyncio.sleep(1)  # Wait for 1 second
    print("   Finished async function")
    return "Done!"

# This doesn't run the function β€” it creates a coroutine
result = simple_async()
print(f"   Type: {type(result)}")
print(f"   Result object: {result}")


# ============================================================
# AWAITING A SINGLE TASK
# ============================================================

print("\n2. AWAITING A SINGLE TASK")

async def wait_one_second():
    """Wait for one second"""
    print("   Waiting...")
    await asyncio.sleep(1)
    print("   Done waiting")
    return "Waited 1 second"

async def run_single():
    result = await wait_one_second()
    print(f"   Result: {result}")

# asyncio.run(run_single())


# ============================================================
# FUNCTIONS THAT DON'T RETURN ANYTHING
# ============================================================

print("\n3. FUNCTIONS THAT RETURN NOTHING")

async def log_message(message):
    """Async function that returns nothing"""
    await asyncio.sleep(0.5)
    print(f"   Log: {message}")

async def run_log():
    await log_message("Hello from async!")
    await log_message("This is another message")

# asyncio.run(run_log())


# ============================================================
# ASYNC FUNCTIONS WITH PARAMETERS
# ============================================================

print("\n4. ASYNC FUNCTIONS WITH PARAMETERS")

async def greet(name, delay):
    """Greet someone after a delay"""
    await asyncio.sleep(delay)
    return f"Hello, {name}! (after {delay}s)"

async def run_greeting():
    result = await greet("Alice", 1)
    print(f"   Result: {result}")
    result2 = await greet("Bob", 0.5)
    print(f"   Result: {result2}")

# asyncio.run(run_greeting())


# ============================================================
# RULES TO REMEMBER
# ============================================================

print("\n" + "-" * 30)
print("RULES FOR ASYNC/AWAIT")
print("-" * 30)
print("""
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ RULES                                                              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 1. You can only use await inside async functions                  β”‚
β”‚                                                                     β”‚
β”‚ 2. An async function always returns a coroutine object             β”‚
β”‚                                                                     β”‚
β”‚ 3. To run async code, you need to use asyncio.run()               β”‚
β”‚                                                                     β”‚
β”‚ 4. Await pauses the function, but doesn't block other functions   β”‚
β”‚                                                                     β”‚
β”‚ 5. Only use await on awaitable things (async functions, tasks)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
""")

async/await syntax key points:

  • async def β€” creates an async function (coroutine)
  • await β€” pauses and waits for something to finish
  • Only inside async β€” you can only use await in async functions
  • Returns coroutine β€” async functions return coroutine objects
  • Need runner β€” use asyncio.run() to run async code

Quick Check: Can you use await outside of an async function? (Answer: No β€” await can only be used inside async functions)

How to Run Async Code

3

Using asyncio.run()

To run async code, you need something called an event loop. The easiest way is to use asyncio.run().

Think of the event loop like a manager that decides which task runs next. It keeps switching between tasks that are waiting, making sure everything gets done.

# How to Run Async Code

import asyncio
import time

print("=" * 50)
print("RUNNING ASYNC CODE")
print("=" * 50)

# ============================================================
# METHOD 1: asyncio.run() (The simple way)
# ============================================================

print("\n1. asyncio.run()")

async def hello_world():
    """A simple async function"""
    print("   Hello!")
    await asyncio.sleep(0.5)
    print("   World!")

# This is the easiest way to run async code
# asyncio.run(hello_world())

print("   βœ… Use asyncio.run() for simple async code")


# ============================================================
# METHOD 2: Creating tasks and running them
# ============================================================

print("\n2. CREATING TASKS")

async def task_one():
    await asyncio.sleep(1)
    return "Task 1 done"

async def task_two():
    await asyncio.sleep(2)
    return "Task 2 done"

async def run_tasks():
    # Create tasks (start them)
    t1 = asyncio.create_task(task_one())
    t2 = asyncio.create_task(task_two())
    
    # Wait for them to finish
    result1 = await t1
    result2 = await t2
    
    return result1, result2

# results = asyncio.run(run_tasks())
# print(f"   Results: {results}")


# ============================================================
# METHOD 3: Running in Jupyter/Interactive
# ============================================================

print("\n3. RUNNING IN JUPYTER NOTEBOOK")

# In Jupyter, you can use:
# await hello_world()

print("   βœ… In Jupyter, you can use await directly")
print("   ❌ In regular Python, you need asyncio.run()")


# ============================================================
# COMPARISON: RUNNING SYNC CODE FROM ASYNC
# ============================================================

print("\n4. RUNNING SYNC CODE FROM ASYNC")

import asyncio

def sync_function():
    """A regular (blocking) function"""
    print("   Running sync function...")
    time.sleep(1)  # This blocks everything!
    print("   Sync function done")
    return "Sync result"

async def run_with_sync():
    print("   Async: Before sync call")
    
    # This will block β€” not good in async code
    result = sync_function()
    
    print(f"   Async: After sync call: {result}")
    return result

# asyncio.run(run_with_sync())


# ============================================================
# RUNNING BLOCKING CODE WITH run_in_executor
# ============================================================

print("\n5. RUNNING BLOCKING CODE (BETTER WAY)")

async def run_sync_properly():
    """Run blocking code without blocking the event loop"""
    print("   Async: Before sync call")
    
    # Run blocking code in a separate thread
    loop = asyncio.get_running_loop()
    result = await loop.run_in_executor(None, sync_function)
    
    print(f"   Async: After sync call: {result}")
    return result

print("   βœ… Use run_in_executor for blocking code")
print("   This prevents blocking the event loop")


# ============================================================
# SUMMARY
# ============================================================

print("\n" + "-" * 30)
print("SUMMARY β€” RUNNING ASYNC CODE")
print("-" * 30)
print("""
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ METHOD                      β”‚ BEST USE CASE                                  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ asyncio.run()               β”‚ Simple programs, scripts                       β”‚
β”‚                             β”‚                                                β”‚
β”‚ create_task()               β”‚ Running multiple tasks together                β”‚
β”‚                             β”‚                                                β”‚
β”‚ await directly (Jupyter)    β”‚ Interactive environments                       β”‚
β”‚                             β”‚                                                β”‚
β”‚ run_in_executor()           β”‚ Running blocking code in async code           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“Œ asyncio.run() is the simplest way to run async code
""")

Running async code key points:

  • asyncio.run() β€” the simplest way to run async code
  • create_task() β€” run multiple tasks concurrently
  • Event loop β€” the engine that manages async tasks
  • run_in_executor β€” run blocking code without blocking

Quick Check: What function do you use to run async code in a regular Python script? (Answer: asyncio.run())

Running Multiple Tasks

4

Running Many Things at Once

The real power of async is running multiple tasks at the same time. Here are the different ways to do it.

# Running Multiple Tasks

import asyncio
import time

print("=" * 50)
print("RUNNING MULTIPLE TASKS")
print("=" * 50)

# ============================================================
# asyncio.gather() β€” Run multiple tasks
# ============================================================

print("\n1. asyncio.gather()")

async def fetch_data(endpoint, delay):
    """Simulate fetching data from an API"""
    print(f"   Fetching from {endpoint}...")
    await asyncio.sleep(delay)
    print(f"   Done fetching from {endpoint}")
    return f"Data from {endpoint}"

async def gather_example():
    # Run all tasks at once and wait for all to finish
    results = await asyncio.gather(
        fetch_data("users", 2),
        fetch_data("posts", 1.5),
        fetch_data("comments", 1)
    )
    return results

# results = asyncio.run(gather_example())
# print(f"   Results: {results}")


# ============================================================
# asyncio.wait() β€” More control
# ============================================================

print("\n2. asyncio.wait()")

async def wait_example():
    tasks = [
        asyncio.create_task(fetch_data("users", 2)),
        asyncio.create_task(fetch_data("posts", 1.5)),
        asyncio.create_task(fetch_data("comments", 1))
    ]
    
    # Wait for all tasks to complete
    done, pending = await asyncio.wait(tasks)
    
    results = []
    for task in done:
        results.append(task.result())
    
    return results

# results = asyncio.run(wait_example())
# print(f"   Results: {results}")


# ============================================================
# asyncio.gather() vs asyncio.wait()
# ============================================================

print("\n3. COMPARISON")

print("""
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ FEATURE             β”‚ asyncio.gather()          β”‚ asyncio.wait()          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Returns             β”‚ List of results           β”‚ (done, pending) tuples  β”‚
β”‚                     β”‚                           β”‚                          β”‚
β”‚ Error handling      β”‚ If one fails, all raise  β”‚ Can handle individually β”‚
β”‚                     β”‚                           β”‚                          β”‚
β”‚ Use case            β”‚ Simple "run all" cases   β”‚ More control needed     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
""")


# ============================================================
# COMPARE SYNC VS ASYNC PERFORMANCE
# ============================================================

print("\n4. COMPARING PERFORMANCE")

async def async_demo():
    print("   Running async tasks...")
    start = time.time()
    
    await asyncio.gather(
        asyncio.sleep(2),
        asyncio.sleep(2),
        asyncio.sleep(2)
    )
    
    print(f"   Async time: {time.time() - start:.2f}s")

def sync_demo():
    print("   Running sync tasks...")
    start = time.time()
    
    time.sleep(2)
    time.sleep(2)
    time.sleep(2)
    
    print(f"   Sync time: {time.time() - start:.2f}s")

print("   ⚑ Async: 3 tasks of 2 seconds = 2 seconds total")
print("   🐒 Sync: 3 tasks of 2 seconds = 6 seconds total")


# ============================================================
# ASYNC WITH DIFFERENT DELAYS
# ============================================================

print("\n5. ASYNC WITH DIFFERENT DELAYS")

async def delayed_task(name, delay):
    """A task with a custom delay"""
    print(f"   Task {name}: starting (will take {delay}s)")
    await asyncio.sleep(delay)
    print(f"   Task {name}: finished")
    return f"Task {name} done in {delay}s"

async def run_mixed():
    # These tasks have different delays
    results = await asyncio.gather(
        delayed_task("A", 3),
        delayed_task("B", 1),
        delayed_task("C", 2)
    )
    
    print(f"   All tasks completed in the order they finished!")
    for result in results:
        print(f"      {result}")

# asyncio.run(run_mixed())


# ============================================================
# HANDLING ERRORS IN GATHER
# ============================================================

print("\n6. HANDLING ERRORS")

async def failing_task(name, fail):
    """A task that might fail"""
    await asyncio.sleep(0.5)
    if fail:
        raise ValueError(f"Task {name} failed!")
    return f"Task {name} succeeded!"

async def run_with_errors():
    try:
        # This will raise if any task fails
        results = await asyncio.gather(
            failing_task("A", False),
            failing_task("B", True),  # This one fails
            failing_task("C", False)
        )
    except ValueError as e:
        print(f"   Error caught: {e}")
    else:
        print(f"   Results: {results}")

print("   βœ… gather() raises an exception if any task fails")

# ============================================================
# RETURN_EXCEPTIONS
# ============================================================

print("\n7. RETURN_EXCEPTIONS β€” Handle failures gracefully")

async def run_with_return_exceptions():
    # Even if tasks fail, return the exceptions as results
    results = await asyncio.gather(
        failing_task("A", False),
        failing_task("B", True),
        failing_task("C", False),
        return_exceptions=True
    )
    
    print("   Results (with exceptions):")
    for result in results:
        if isinstance(result, Exception):
            print(f"      {result}")
        else:
            print(f"      {result}")

print("   βœ… return_exceptions=True lets you handle failures individually")

# asyncio.run(run_with_return_exceptions())

Multiple tasks key points:

  • gather() β€” runs multiple tasks and returns all results
  • wait() β€” gives more control over task completion
  • create_task() β€” starts a task without waiting
  • return_exceptions β€” handles failures gracefully

Quick Check: What function runs multiple async tasks and waits for all to finish? (Answer: asyncio.gather())

Real-World Example

5

Fetching Data from Multiple APIs

# Real-World Example: Fetching Data from APIs

import asyncio
import time
import random

print("=" * 60)
print("FETCHING DATA FROM MULTIPLE APIs")
print("=" * 60)

# ============================================================
# SIMULATED API CALLS
# ============================================================

async def fetch_api(name, delay, success_rate=0.9):
    """Simulate fetching data from an API"""
    print(f"   🌐 Calling {name} API...")
    
    # Simulate network delay
    await asyncio.sleep(delay)
    
    # Simulate occasional failure
    if random.random() > success_rate:
        raise Exception(f"{name} API failed!")
    
    print(f"   βœ… {name} API responded")
    return {
        "api": name,
        "data": f"Data from {name}",
        "delay": delay
    }


# ============================================================
# FETCH MULTIPLE APIS
# ============================================================

async def fetch_all_apis():
    """Fetch data from multiple APIs concurrently"""
    print("\n   Starting API calls...")
    
    apis = [
        fetch_api("Users", 1.5, 0.95),
        fetch_api("Posts", 2.0, 0.9),
        fetch_api("Comments", 1.0, 0.85),
        fetch_api("Profiles", 0.8, 0.95)
    ]
    
    # Run all API calls together
    start = time.time()
    results = await asyncio.gather(*apis, return_exceptions=True)
    elapsed = time.time() - start
    
    print(f"\n   All API calls completed in {elapsed:.2f}s")
    
    # Process results
    success_count = 0
    for result in results:
        if isinstance(result, Exception):
            print(f"      ❌ {result}")
        else:
            print(f"      βœ… {result['api']}: {result['data']} (took {result['delay']}s)")
            success_count += 1
    
    print(f"\n   Summary: {success_count}/{len(apis)} APIs succeeded")


# ============================================================
# FETCH WITH TIMEOUT
# ============================================================

async def fetch_with_timeout(api_name, delay, timeout):
    """Fetch data with a timeout"""
    try:
        result = await asyncio.wait_for(
            fetch_api(api_name, delay),
            timeout=timeout
        )
        return result
    except asyncio.TimeoutError:
        return f"❌ {api_name} timed out after {timeout}s"


async def fetch_with_timeouts():
    """Fetch data with different timeouts"""
    print("\n   Fetching with timeouts...")
    
    results = await asyncio.gather(
        fetch_with_timeout("Fast API", 0.5, 1.0),
        fetch_with_timeout("Slow API", 2.0, 1.0),
        fetch_with_timeout("Medium API", 1.0, 1.0),
        return_exceptions=True
    )
    
    for result in results:
        print(f"      {result}")


# ============================================================
# PROCESS DATA AS IT ARRIVES
# ============================================================

async def process_as_it_arrives():
    """Process data as soon as each API responds"""
    print("\n   Processing data as it arrives...")
    
    apis = [
        fetch_api("API 1", 2.0),
        fetch_api("API 2", 1.0),
        fetch_api("API 3", 1.5),
        fetch_api("API 4", 0.5)
    ]
    
    # Create tasks
    tasks = [asyncio.create_task(api) for api in apis]
    
    # Process as they complete
    for task in asyncio.as_completed(tasks):
        try:
            result = await task
            print(f"      πŸ“Š Processed: {result['api']} (took {result['delay']}s)")
        except Exception as e:
            print(f"      ❌ Error: {e}")


# ============================================================
# DEMONSTRATION
# ============================================================

print("\n1. FETCHING ALL APIS")
asyncio.run(fetch_all_apis())

print("\n2. FETCHING WITH TIMEOUTS")
asyncio.run(fetch_with_timeouts())

print("\n3. PROCESSING AS IT ARRIVES")
asyncio.run(process_as_it_arrives())


# ============================================================
# KEY TAKEAWAYS
# ============================================================

print("\n" + "=" * 60)
print("KEY TAKEAWAYS")
print("=" * 60)
print("""
βœ… Async/await is great for I/O-bound operations
βœ… API calls, database queries, file operations
βœ… Multiple requests can be done concurrently
βœ… Use gather() for multiple tasks
βœ… Use wait_for() for timeouts
βœ… Process results as they arrive with as_completed()
βœ… Async saves time β€” 4 APIs at 2s each = 2s total vs 8s sync
""")

Real-world example key points:

  • API calls β€” perfect for async because they wait for network
  • gather() β€” runs multiple API calls together
  • Timeout β€” use wait_for() to prevent hanging
  • as_completed() β€” process results as they arrive
  • Massive speedup β€” 4 APIs at 2s each = 2s async vs 8s sync

Quick Check: When is async/await most useful? (Answer: When doing I/O operations like API calls, database queries, and file operations)

Best Practices

6

Using Async/Await Effectively

# Best Practices for Async/Await

import asyncio
import time

print("=" * 60)
print("BEST PRACTICES FOR ASYNC/AWAIT")
print("=" * 60)

# ============================================================
# 1. USE ASYNC ONLY FOR I/O OPERATIONS
# ============================================================

print("\n1. USE ASYNC ONLY FOR I/O OPERATIONS")

# ❌ BAD: Using async for CPU-bound work
async def bad_async():
    # This does heavy computation β€” async won't help!
    total = 0
    for i in range(1000000):
        total += i
    return total

# βœ… GOOD: Use async for I/O operations
async def good_async():
    # This waits for external things
    await asyncio.sleep(0.1)
    return "API response"

print("   βœ… Use async for I/O: network, files, databases")
print("   ❌ Don't use async for CPU: math, loops, calculations")


# ============================================================
# 2. DON'T MIX ASYNC AND SYNC BLOCKING CODE
# ============================================================

print("\n2. DON'T MIX ASYNC AND SYNC BLOCKING CODE")

# ❌ BAD: Blocking code in async function
async def bad_mix():
    print("   Async: before blocking")
    time.sleep(1)  # This blocks the whole event loop!
    print("   Async: after blocking")

# βœ… GOOD: Use run_in_executor for blocking code
async def good_mix():
    print("   Async: before blocking")
    loop = asyncio.get_running_loop()
    result = await loop.run_in_executor(None, time.sleep, 1)
    print("   Async: after blocking")

print("   ❌ Don't use time.sleep() in async functions")
print("   βœ… Use asyncio.sleep() or run_in_executor")


# ============================================================
# 3. USE ASYNCIO.GATHER FOR MULTIPLE TASKS
# ============================================================

print("\n3. USE ASYNCIO.GATHER FOR MULTIPLE TASKS")

# ❌ BAD: Awaiting tasks one by one
async def bad_gather():
    result1 = await asyncio.sleep(1)
    result2 = await asyncio.sleep(1)
    result3 = await asyncio.sleep(1)
    return "Done"

# βœ… GOOD: Using gather for concurrent execution
async def good_gather():
    results = await asyncio.gather(
        asyncio.sleep(1),
        asyncio.sleep(1),
        asyncio.sleep(1)
    )
    return "Done"

print("   βœ… Use gather() for concurrent tasks")
print("   ❌ Don't await tasks one by one (it's sequential)")


# ============================================================
# 4. SET TIMEOUTS
# ============================================================

print("\n4. SET TIMEOUTS")

async def slow_task():
    await asyncio.sleep(10)
    return "Done"

async def with_timeout():
    try:
        result = await asyncio.wait_for(slow_task(), timeout=2)
        print(f"   Result: {result}")
    except asyncio.TimeoutError:
        print("   ⏰ Task timed out!")

print("   βœ… Always set timeouts for external calls")


# ============================================================
# 5. HANDLE EXCEPTIONS
# ============================================================

print("\n5. HANDLE EXCEPTIONS")

async def might_fail():
    await asyncio.sleep(0.5)
    raise ValueError("Something went wrong!")

async def with_error_handling():
    try:
        result = await might_fail()
        print(f"   Result: {result}")
    except ValueError as e:
        print(f"   ❌ Caught error: {e}")

print("   βœ… Always handle exceptions in async code")


# ============================================================
# 6. USE RETURN_EXCEPTIONS IN GATHER
# ============================================================

print("\n6. USE RETURN_EXCEPTIONS")

async def with_return_exceptions():
    results = await asyncio.gather(
        asyncio.sleep(0.1),
        might_fail(),
        asyncio.sleep(0.1),
        return_exceptions=True
    )
    
    for result in results:
        if isinstance(result, Exception):
            print(f"      ❌ {result}")
        else:
            print(f"      βœ… {result}")

print("   βœ… Use return_exceptions to handle failures gracefully")


# ============================================================
# 7. SUMMARY
# ============================================================

print("\n" + "=" * 60)
print("BEST PRACTICES SUMMARY")
print("=" * 60)
print("""
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PRACTICE                    β”‚ WHY IT MATTERS                             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Use async for I/O only      β”‚ Async doesn't help CPU-bound work          β”‚
β”‚                             β”‚                                             β”‚
β”‚ Don't block the event loop  β”‚ Blocking code defeats async performance    β”‚
β”‚                             β”‚                                             β”‚
β”‚ Use gather() for concurrencyβ”‚ Run multiple tasks together                β”‚
β”‚                             β”‚                                             β”‚
β”‚ Set timeouts                β”‚ Prevent hanging on slow operations         β”‚
β”‚                             β”‚                                             β”‚
β”‚ Handle exceptions           β”‚ Don't let errors crash everything          β”‚
β”‚                             β”‚                                             β”‚
β”‚ Use return_exceptions       β”‚ Handle failures individually               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“Œ REMEMBER:
   β€’ Async is for I/O, not CPU
   β€’ Don't block the event loop
   β€’ Always set timeouts
   β€’ Handle errors gracefully
""")

Best practices summary:

  • Use async for I/O β€” not for CPU-bound work
  • Don't block the event loop β€” use asyncio.sleep() not time.sleep()
  • Use gather() β€” for concurrent tasks
  • Set timeouts β€” prevent hanging
  • Handle exceptions β€” don't let errors crash everything

Quick Check: Should you use async for CPU-heavy operations? (Answer: No β€” async is for I/O operations)

Try It Yourself

Experiment with async/await in the editor below.

Loading Pyodide... 0%
Python Code Editor
==================================================
ASYNC/AWAIT - PRACTICE
==================================================

1. BASIC ASYNC FUNCTION

2. RUNNING MULTIPLE TASKS

3. TIMEOUTS

4. COMPARE SYNC VS ASYNC
Running sync (one at a time):
Sync time: 1.00s
Running async (together):
Async time: 0.50s
πŸ†

You've Got It!

You now understand async/await in Python. You know how to write async functions, run multiple tasks together, and handle errors and timeouts.

Quick Quiz

Test what you've learned:

1. What keyword is used to define an async function?
2. What does await do?
3. What function runs async code in a script?
4. What function runs multiple async tasks together?
5. When should you use async/await?

Frequently Asked Questions

What is async/await in Python? β–Ό

Async/await is a way to write code that can do multiple things at once without waiting for each thing to finish. It's used for I/O operations like API calls, database queries, and file operations.

Does async make code run faster? β–Ό

Async makes code faster when you're waiting for external things (I/O). It doesn't help with CPU-heavy work. For API calls, file reads, and database queries, async can be much faster because it doesn't wait idly.

Can I use await outside an async function? β–Ό

No! You can only use await inside functions defined with async def. If you try to use it elsewhere, Python will give you a syntax error.

What's the difference between async and threads? β–Ό

Threads run multiple things at the same time (parallel). Async runs one thing at a time but switches between tasks when they're waiting (concurrent). Async is usually simpler and uses less memory than threads.

Is asyncio part of the standard library? β–Ό

Yes! asyncio is part of Python's standard library. You don't need to install anything extra. Just import it with import asyncio.

Can I use async with Django or Flask? β–Ό

Yes! Django 3.1+ supports async views. Flask doesn't have built-in async support, but you can use Quart (an async version of Flask) or FastAPI (which is built for async from the start).

Where to Go From Here

Now that you understand async/await in Python, check out these related topics:

Context Managers

Learn how context managers work with async code.

Learn More β†’

Generators

Learn about generators β€” another way to write async code.

Learn More β†’

Decorators

Learn how to create async decorators.

Learn More β†’
Interview Resources
  • Python Syntax & Variables Interview Questions
  • Top SQL Interview Questions & Answers
  • SQL Joins: Displaying Data from Multiple Tables FAQ
  • Python Lists and Dictionaries Interview Questions
  • Python OOP Interview Questions
  • SQL Set Operators Interview Questions
Previous: Type Hints Next: Dataclasses β†’