Why gather() Silently Ate My Exception and Delayed Everything Else
I had a scraper hitting 50 API endpoints concurrently. One endpoint started returning 500s. The entire batch hung for the full timeout window before failing — even though 49 endpoints succeeded in under 2 seconds.
The problem? I used asyncio.gather() without return_exceptions=True. The failing task raised an exception that bubbled up immediately, canceling all other tasks. But because I didn't catch it properly, the event loop waited for cleanup. The symptom looked like a hang.
Switching to asyncio.as_completed() fixed it — successful responses came back immediately, and I handled failures as they occurred. But that wasn't the end of the story. When I migrated to Python 3.11, TaskGroup gave me structured concurrency and automatic cleanup that neither of the old patterns provided.
Here's what I learned about when each pattern actually makes sense.
gather(): Fast When Everything Succeeds, Fragile When Anything Fails
The signature looks simple:
python
---
*Continue reading the full article on [TildAlice](https://tildalice.io/asyncio-gather-as-completed-taskgroup-patterns/)*

Top comments (0)