Table of Contents

    async

    CONCURRENCY & PROGRAMMING

    Async Programming — The Complete Guide

    Understand asynchronous programming, how async/await works, and how to write non-blocking, high-performance code across languages.

    Introduction

    Asynchronous (async) programming is a style of writing code that lets a program start a long-running task and continue doing other work without waiting for that task to finish. When the task completes, the program picks up its result. This avoids "blocking" — where the whole program sits idle waiting for a slow operation like a network call, disk read, or database query.

    In one line: Async programming lets your code do other work while waiting, instead of freezing until a slow task finishes.

    Real-World Analogy

    The Coffee Shop Barista

    A good barista takes your order, starts the espresso machine, and immediately serves the next customer instead of standing and staring at the machine. When your coffee is ready, they hand it over. That is async — start the slow task, keep serving others, and deliver results when they are ready.

    Synchronous vs Asynchronous

    Synchronous (Blocking)

    • Tasks run one after another
    • Each task must finish before the next starts
    • Program waits idle during slow I/O
    • Simple to reason about, but wastes time
    • One slow call freezes everything

    Asynchronous (Non-Blocking)

    • Slow tasks run in the background
    • Program continues other work meanwhile
    • Results collected when ready
    • Far better throughput for I/O
    • Responsive UIs and scalable servers

    Prerequisites

    Before You Start

    • Understanding of functions, return values, and control flow
    • Basic knowledge of blocking vs non-blocking I/O
    • Familiarity with the concept of the event loop
    • At least one runtime installed (Node.js, Python 3.7+, or .NET)
    • Comfort reading callbacks and promises (helpful, not required)

    Core Building Blocks

    1

    The Event Loop

    The engine that drives async code.

    A loop that continuously checks for completed tasks and runs their callbacks/continuations, keeping a single thread busy and responsive.

    2

    Promise / Future / Task

    A placeholder for a value that isn't ready yet.

    It represents an ongoing operation and eventually resolves with a result (success) or rejects with an error (failure).

    3

    async / await

    Syntax that makes async code read like sync code.

    async marks a function as asynchronous; await pauses it until a promise resolves — without blocking the thread.

    Evolution of Async Patterns

    Pattern Description Drawback
    Callbacks Pass a function to run when the task finishes Nested "callback hell", hard to read
    Promises / Futures Chainable objects representing a future value Long .then() chains still get messy
    async / await Write async code that looks synchronous Easy to accidentally serialize tasks

    Async in JavaScript

    JavaScript's async model is built on Promises with async/await syntax on top.

    The Callback Era (for context)

    // Old style — nested callbacks ("callback hell")
    getUser(1, function (user) {
        getPosts(user.id, function (posts) {
            getComments(posts[0].id, function (comments) {
                console.log(comments);
            });
        });
    });

    Modern async / await

    async function loadData() {
        try {
            const user = await getUser(1);
            const posts = await getPosts(user.id);
            const comments = await getComments(posts[0].id);
            console.log(comments);
        } catch (err) {
            console.error("Failed:", err);
        }
    }
    
    loadData();

    Running tasks concurrently

    // Both requests run at the same time
    async function fetchBoth() {
        const [a, b] = await Promise.all([
            fetch("/api/a"),
            fetch("/api/b"),
        ]);
        return [await a.json(), await b.json()];
    }
    Tip Use Promise.all to run independent async tasks in parallel — much faster than awaiting them one by one.

    Async in Python (asyncio)

    Python uses async def to define coroutines and await to suspend on awaitables.

    import asyncio
    import aiohttp
    
    async def fetch(session, url):
        async with session.get(url) as response:
            return await response.text()
    
    async def main():
        urls = ["https://example.com", "https://python.org"]
        async with aiohttp.ClientSession() as session:
            # gather runs all requests concurrently
            results = await asyncio.gather(
                *(fetch(session, url) for url in urls)
            )
        print(f"Fetched {len(results)} pages")
    
    asyncio.run(main())

    Async in C# (.NET)

    C# pioneered async/await using Task and Task<T> as its future type.

    using System;
    using System.Net.Http;
    using System.Threading.Tasks;
    
    class Program
    {
        static async Task Main()
        {
            using var client = new HttpClient();
    
            // Start both downloads concurrently
            Task<string> a = client.GetStringAsync("https://example.com");
            Task<string> b = client.GetStringAsync("https://dotnet.microsoft.com");
    
            string[] pages = await Task.WhenAll(a, b);
            Console.WriteLine($"Downloaded {pages.Length} pages");
        }
    }

    How async/await Works Internally

    1. An async function runs normally until it hits an await.
    2. If the awaited task isn't done, the function suspends and returns control to the event loop.
    3. The event loop runs other ready tasks while the slow operation continues in the background.
    4. When the awaited task completes, the event loop resumes the function right after the await.
    5. The function continues with the resolved value until it finishes or awaits again.

    Why Async Is Faster for I/O

    Suppose you have \(N\) independent I/O tasks, each taking time \(t\). Run sequentially (sync), the total time is the sum:

    \[ T_{\text{sync}} = \sum_{i=1}^{N} t_i \approx N \times t \]

    Run concurrently (async), they overlap, so the total is close to the single longest task:

    \[ T_{\text{async}} \approx \max_{1 \le i \le N} t_i \]

    For many similar I/O-bound tasks, async can reduce total wait time from \(N \times t\) down to roughly \(t\).

    When to Use Async (and When Not To)

    Workload Use Async? Why
    Network / API calls Yes Mostly waiting — ideal for non-blocking I/O
    Database queries Yes I/O-bound; frees the thread while waiting
    File reads/writes Yes Disk I/O benefits from async
    Heavy CPU computation No Use threads/processes — async won't parallelize CPU work

    Best Practices

    Do This

    • Use Promise.all / gather / WhenAll to run independent tasks concurrently
    • Always wrap awaited calls in try/catch to handle rejections
    • Never mix blocking calls into async code — use async equivalents
    • Avoid async void in C#; prefer async Task
    • Add timeouts and cancellation to long-running operations
    • Don't use async for pure CPU-bound work — offload to threads/processes

    Common Mistakes

    Bad Awaiting independent tasks one by one: await taskA; await taskB; — this runs them sequentially and wastes time.
    Good Running them together: await Promise.all([taskA, taskB]) — they overlap and finish faster.
    Bad Forgetting to await a promise — the code continues before the result is ready, causing bugs.

    Interview Questions

    Question Short Answer
    What is asynchronous programming? A model where slow tasks run without blocking, letting the program do other work meanwhile.
    What is the event loop? A loop that schedules and runs completed async tasks' continuations on a thread.
    Difference between async and multithreading? Async is about non-blocking waiting (mostly one thread); threads run code in parallel.
    What does await do? Suspends the async function until the awaited promise/task resolves, without blocking.
    When should you NOT use async? For CPU-bound work — async won't speed it up; use threads or processes instead.

    Quick Revision

    Language Future Type Run Concurrently
    JavaScript Promise Promise.all()
    Python Coroutine / Future asyncio.gather()
    C# Task / Task<T> Task.WhenAll()
    Rust Future join!()

    Key Takeaways

    Async programming keeps your app responsive and scalable by not blocking while waiting on slow I/O. Use async/await for readable non-blocking code, run independent tasks concurrently, and reserve threads/processes for CPU-bound work.