Tech News
← Back to articles

Await Is Not a Context Switch: Understanding Python's Coroutines vs. Tasks

read original related products more articles

The latest blog posts, release news, and automation tips straight in your inbox

The latest blog posts, release news, and automation tips straight in your inbox

Python’s async model is misunderstood, especially by engineers coming from JS or C#. In Python, awaiting a coroutine doesn’t yield to the event loop. Only tasks create concurrency. This post explains why that distinction matters and how it affects locking, design, and correctness.

Every engineer has had that moment during a review where a comment sticks in their head longer than it should.

In my case, it was a simple suggestion:

“You should add more locks here: this code is async, so anything might interleave.”

The code in question touched a shared cache, and on the surface the comment made sense. Multiple asyncio tasks were hitting the same structure, and the function modifying it was async. Shouldn't that mean I need more locks?

That review pushed me down a rabbit hole. Not about the cache (it was tiny) but about the mental model many engineers (including experienced ones) bring to Python's async system. A model shaped by JavaScript or C#: all languages where await means "yield to the runtime now."

But Python isn't those languages. And misunderstanding this fundamental difference leads to unnecessary locking, accidental complexity, and subtle bugs.

This post is the explanation I wish more engineers had.

... continue reading