Python24 min read

Python Async Await

Learn async and await with a clear mental model, understand concurrency vs parallelism, and write faster I/O programs like API callers and web scrapers.

David Miller
August 14, 2025
5.8k285

Async programming helps when your program spends time waiting, like:
- calling APIs
- reading files from disk
- talking to databases
- downloading images

      Instead of waiting and doing nothing, async lets Python start another task while the first one is waiting.
      
      ## First, understand the idea (very important)
      
      ### Synchronous (normal code)
      One task runs at a time. If task A is waiting, everything waits.
      
      ### Asynchronous (async code)
      Task A can pause (await) and Python can run task B during that pause.
      
      ## Concurrency vs Parallelism
      
      - **Concurrency**: tasks take turns (great for I/O waiting)
      - **Parallelism**: tasks truly run at the same time (great for CPU-heavy work)
      
      Async is mainly for **concurrency** (I/O work).
      
      ## Your first async function
      
      ```python
      import asyncio
      
      async def greet(name):
          print(f"Hello {name}!")
          await asyncio.sleep(1)  # pretend we are waiting for network
          print(f"Goodbye {name}!")
      
      asyncio.run(greet("Tom"))
      ```
      
      Expected output:
      ```
      Hello Tom!
      (wait 1 second)
      Goodbye Tom!
      ```
      
      ### Why do we need 'await'?
      Because it tells Python: "I am waiting here. You can run other tasks."
      
      ## Running multiple tasks at once
      
      ```python
      import asyncio
      
      async def fetch_data(task_id):
          print(f"Task {task_id}: start")
          await asyncio.sleep(2)
          print(f"Task {task_id}: end")
          return f"data-{task_id}"
      
      async def main():
          results = await asyncio.gather(
              fetch_data(1),
              fetch_data(2),
              fetch_data(3),
          )
          print(results)
      
      asyncio.run(main())
      ```
      
      Expected output (order may vary):
      ```
      Task 1: start
      Task 2: start
      Task 3: start
      (wait ~2 seconds total)
      Task 1: end
      Task 2: end
      Task 3: end
      ['data-1', 'data-2', 'data-3']
      ```
      
      Notice: total time is about 2 seconds, not 6 seconds. That is the power of async.
      
      ## Real-world example: async HTTP requests
      
      For real async HTTP, we usually use `aiohttp`.
      
      ```python
      import asyncio
      import aiohttp
      
      async def fetch_url(session, url):
          async with session.get(url) as resp:
              return await resp.text()
      
      async def main():
          async with aiohttp.ClientSession() as session:
              html = await fetch_url(session, "https://example.com")
              print(html[:120])
      
      asyncio.run(main())
      ```
      
      ## Common beginner mistakes
      
      ### Mistake 1: forgetting await
      ```python
      # wrong idea:
      # result = fetch_data(1)  # this returns a coroutine, not the final data
      ```
      
      ### Mistake 2: using blocking code inside async
      ```python
      # Avoid time.sleep() inside async code
      # Use: await asyncio.sleep()
      ```
      
      ## Graph: how async scheduling works
      
      ```mermaid
      flowchart LR
        A[Task 1 starts] --> B[await: waiting]
        B --> C[Event loop runs Task 2]
        C --> D[Task 2 await]
        D --> E[Event loop runs Task 3]
        E --> F[Task 3 await]
        F --> G[Resume Task 1 when ready]
      ```
      
      ## Key points
      
      - Use async for I/O-bound work
      - await = pause here, let others run
      - asyncio.gather runs tasks concurrently
      - For CPU-heavy work use multiprocessing (not async)
      
#Python#Advanced#Async