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

Python’s async twist: “await” doesn’t pause, and devs are arguing about it

TLDR: Python’s async model says awaiting a coroutine doesn’t automatically pause; only tasks create real concurrency. Commenters spar over Java’s virtual threads, JS parallels, and lock-happy war stories, turning a subtle concept into a cross-language showdown that matters for writing correct, simpler async code.

Python just dropped a brain-bender: in Python, await doesn’t automatically hand control back—only tasks do. That means you can step into an async function like a normal call until you hit something that truly needs to wait. Cue the comment-section fireworks. Rust folks marched in first, with oersted coolly noting it’s the same vibe in Rust’s tokio world. Then the Java crowd lit the torches: vamega says the article misreads Java’s virtual threads, arguing they use preemptive (the runtime can interrupt you) not cooperative scheduling. Translation: Java doesn’t only pause at obvious wait points.

Meanwhile, a JS dev, pandaxtc, asks the spicy question: wouldn’t the first example behave the same in JavaScript anyway? The thread devolves into a calibration of “what exactly counts as a suspension point” and whether Python is being unfairly framed as quirky when it’s just different. Enter reactordev with a popcorn-worthy “This is gold” and a horror tale about a ‘Python guru’ manager who went lock-happy and turned coroutines into chaos.

For the “just give me tools” crowd, sandblast plugs Effection for JavaScript to mimic Python-style behavior, while pandaxtc tosses a StackOverflow link to sharpen the example game. The meme of the day: “Await isn’t a context switch” — tattooed on forearms and code reviews alike. Verdict: fewer panic locks, more precise mental models, and endless cross-language sniping.

Key Points

  • In Python, awaiting a coroutine does not yield to the event loop; only tasks cause yielding and concurrency.
  • Python separates coroutines (async def) from tasks (asyncio.create_task); the event loop interleaves tasks, not coroutines.
  • In JavaScript and C#, await is always a suspension point, and async functions return Promise or Task/Task<T>, unlike Python.
  • A coroutine executes synchronously until it reaches an awaitable that is not ready (e.g., await asyncio.sleep), preventing interleaving before that point.
  • Concurrency and correct locking should be based on actual suspension points, not merely on functions being async.

Hottest takes

“Virtual threads are preemptive, not co‑operative” — vamega
“Wouldn’t the first example behave the same in Javascript?” — pandaxtc
“This is gold. Here’s a horror story…” — reactordev
Made with <3 by @siedrix and @shesho from CDMX. Powered by Forge&Hive.