Skip to main content
CodeFlow
control flow~16 minRoute 04 3 of 3

Async & Concurrency

Doing other work while waiting — without freezing the program.

Prerequisites:Functions

Story — the chef who orders pizza

A chef has a 30-minute pizza in the oven. While it bakes, they don't stand staring at the door. They chop salad, set the table, answer the phone — and when the timer beeps, they pull the pizza out. The kitchen looks busy because waiting time is reused.

Async is that. When your code says await fetch(url), the runtime puts that work on a list and runs other code while the network does its job. When the result arrives, the original function picks up where it left off. One thread, many things in flight.

Awaita says

await means "pause this function until the result is ready." It does not mean "freeze the program." The rest of the world keeps running.

See it — sequential vs concurrent

Two ways to run two async tasks: sequentially (await one, then await the other — total time is the sum) or concurrently (kick both off, then await both — total time is the max). The same code, two shapes, very different latency.

Awaita says

await is "pause this function until the result is ready, but let the rest of the program keep running." Not the same as a thread — usually the same thread, time-sharing.

Try it — when does async help?

Exercise. Which of these workloads benefits most from async/await on a single thread?

  • (a) A program that does heavy math on a 1 GB array.
  • (b) A web server handling 1,000 simultaneous requests, mostly waiting on the database.
  • (c) A script that reads a file, transforms it, and writes a new one.
Reveal answer

(b) is the canonical async win. Each request spends 99% of its time waiting on I/O — async lets one thread interleave hundreds. (a) is CPU-bound — async helps nothing; you need parallelism (multiple cores). (c) might benefit slightly if you can read and write at once, but for a small script it doesn't matter.

Code it — five languages, two patterns

Python and JavaScript share async/await almost identically. Java uses CompletableFuture. Go avoids the colour-the-functions problem entirely with goroutines + channels. Different shapes, same idea.

import asyncio

async def fetch_user(id):
    await asyncio.sleep(1)        # simulate network
    return {"id": id, "name": f"User{id}"}

async def main():
    # Sequential — total ~2s
    a = await fetch_user(1)
    b = await fetch_user(2)

    # Concurrent — total ~1s
    a, b = await asyncio.gather(fetch_user(1), fetch_user(2))

asyncio.run(main())

Quiz it — make it stick

  1. Question 1

    What's the difference between concurrency and parallelism?

  2. True or false — Question 2

    In JavaScript, awaiting two promises sequentially with `await` always takes the sum of their durations.

  3. Predict — Question 3

    What does this print? (Each fetch takes ~1s)

    // JavaScript
    const t0 = performance.now();
    const [a, b, c] = await Promise.all([
      fetchUser(1), fetchUser(2), fetchUser(3),
    ]);
    console.log(`took ${Math.round(performance.now() - t0)}ms`);

No Dumb Questions

Real questions other learners asked on this page.

  • What's the difference between concurrency and parallelism?
    Concurrency = many things in progress (interleaved). Parallelism = many things actually executing simultaneously (multiple cores). Async/await is concurrency on one thread; threads/processes are parallelism.
  • Why does async code "infect" everything?
    Because to await a promise, the surrounding function must itself be async (or use a callback). It propagates upward until you reach the top of the program. Some languages call this "function colouring."
  • What is a race condition?
    Two operations that interleave in a way the programmer didn't anticipate, producing wrong results. Classic example: two threads incrementing the same counter — they read 5, both add 1, both write 6, and the count is off by one.