Ch. 9 · Python

Python asyncio.gather and Timeouts

Run coroutines concurrently with asyncio.gather, enforce timeouts, and handle partial failures and cancellation correctly.

~2 min readadvancedupdated Oct 5, 2026

asyncio.gather schedules several awaitables at once and returns their results in order. It is the standard way to run independent I/O concurrently, but its error and cancellation behavior decides whether a failure of one task stops the others.

Before you start

You should be comfortable with async/await and coroutines. This article assumes Python 3.11 or later, where asyncio.timeout is available.

Step-by-step walkthrough

Step 1: Gather independent coroutines

Pass coroutines to asyncio.gather and await the result; they run concurrently, and the return value is a list in the original order regardless of completion order. Because they share the event loop, use it for I/O-bound work, not CPU-bound work.

Step 2: Bound the whole operation

Wrap the gather in async with asyncio.timeout(seconds) to cancel the group if it runs too long. Cancellation raises TimeoutError at the await point and propagates into the child coroutines, which should handle CancelledError to release resources.

Step 3: Decide how failures propagate

By default, if one awaitable raises, gather cancels the others and raises that exception. Pass return_exceptions=True to collect exceptions as values instead, which is useful when you want the successful results even if some tasks fail.

Worked scenario

The two coroutines run concurrently and the results keep their order.

import asyncio

async def fetch(name: str, delay: float) -> str:
    await asyncio.sleep(delay)
    return name

async def main() -> None:
    results = await asyncio.gather(fetch('a', 0.1), fetch('b', 0.2))
    print(results)

asyncio.run(main())
python

Walk through the example

gather starts both coroutines, and they sleep at the same time, so the total wait is about the slower delay rather than the sum. The result list is ['a', 'b'] in argument order even though b finishes later. Awaiting them sequentially would have taken the sum of the delays instead.

Common mistake

Awaiting coroutines one after another and calling it concurrency, which serializes the I/O. Another is ignoring cancellation: a task that swallows CancelledError prevents the timeout from working and can leave the loop waiting on work that should have stopped.

Verify the behavior

Time the gather against the sum of the delays and confirm it is closer to the maximum. Trigger a timeout and assert TimeoutError propagates and the children stop. Run with return_exceptions=True, make one task raise, and confirm the other results are still returned alongside the exception.

Interview exercise

One of three gathered tasks always fails. How do you still get the other two results?

Answer and reasoning

Use asyncio.gather(task1, task2, task3, return_exceptions=True), which returns each result or exception as a value instead of raising. Then inspect the list and decide how to report the failure. Without that flag, the first exception cancels the siblings and raises, so the successful results are lost.

Continue learning

Compare concurrency limits in Bounded async worker queues and asyncio blocking. Read the Python asyncio task documentation and try the Python interview questions.

More in Python

read ✓Python · mid

Python Asyncio and Blocking Functions

Python Asyncio and Blocking Functions. Learn the reasoning, a practical example, common mistakes and an interview exercise.

~2 min readread →
read ✓Python · mid

Python Asyncio Tasks and Cancellation

Python Asyncio Tasks and Cancellation. Learn the reasoning, a practical example, common mistakes and an interview exercise.

~2 min readread →
read ✓Python · mid

Python Bounded Async Worker Queues

Use asyncio.Queue and a fixed worker pool to bound pending work. Trace backpressure, shutdown and failure ownership.

~3 min readread →
esc