Real concurrency with asyncio.gather()

async/await alone doesn't give you concurrency

This is worth demonstrating directly, since it’s a natural assumption to make: simply rewriting the three API calls from Concept 1 as coroutines, and await-ing each one, does not make them run concurrently:

Try it — edit and run

Still 3.5 seconds — every await still fully waits for that specific coroutine to finish before moving to the next line, exactly like a regular function call would. async/await alone just gives you the ability to pause and resume — it doesn’t automatically run multiple things at once. Something has to actually schedule them to overlap; that’s what asyncio.gather() does.

asyncio.gather() — actually running things concurrently

asyncio.gather() takes multiple coroutines and runs them concurrently, returning all their results together once every one of them has finished:

Try it — edit and run

Two things to notice. First, all three "calling ... API..." lines print immediately, one after another — every coroutine starts right away, rather than waiting for the previous one to finish. Second, the total time is now roughly 2.0 seconds — the single longest wait, not the sum of all three. While call_search_api() is in the middle of its 2-second wait, the other two are also waiting, at the same time, instead of sitting in line — exactly the overlap Concept 1 identified as the actual goal.

asyncio.gather() is passed each coroutine directly — note call_search_api() here, calling the function to produce a coroutine object, not await-ing it individually; gather() itself is what you await, and it takes care of running all three underneath that one await.

Results come back in the order you passed them, not the order they finish

results above is ['3 results found', '72F and sunny', '4'] — matching the order call_search_api, call_weather_api, call_calculator_api were passed to gather(), even though call_calculator_api() (a half-second wait) actually finishes first, well before call_search_api()’s two-second wait completes:

Try it — edit and run

This makes gather()’s results reliably unpackable — the same tuple-unpacking mechanism from the data structures lessonsearch, weather, calc = await asyncio.gather(...) always lines up correctly with which call is which, regardless of which one actually completed fastest behind the scenes.

Check your understanding
1/5

Rewriting three sequential API calls as coroutines and await-ing each one in turn, without gather() — does this run them concurrently?

Exercise · Graded

Implement load_profile so all three fetches run concurrently via asyncio.gather(), returning their results as a tuple in the order (user, permissions, settings).