[{"data":1,"prerenderedAt":70},["ShallowReactive",2],{"qa-\u002Fpython\u002Fconcurrency\u002Fasyncio":3},{"page":4,"siblings":57,"blog":48},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"related":48,"seo":49,"seoDescription":50,"stem":51,"subtopic":52,"topic":53,"topicSlug":54,"updated":55,"__hash__":56},"qa\u002Fpython\u002Fconcurrency\u002Fasyncio.md","Asyncio",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"hard","md","Python","python",{},true,3,"\u002Fpython\u002Fconcurrency\u002Fasyncio",[23,27,32,36,40,44],{"id":24,"difficulty":14,"q":25,"a":26},"what-is-asyncio","What is asyncio and the event loop?","**asyncio** is Python's framework for **single-threaded concurrency** using an\n**event loop**. The **event loop** is a scheduler that runs many **coroutines**\ncooperatively: when one coroutine **awaits** something (typically I\u002FO), it\n**yields control** back to the loop, which runs another ready coroutine while the\nfirst waits. No thread is blocked sitting idle.\n\n```python\nimport asyncio\n\nasync def main():\n    print(\"hello\")\n    await asyncio.sleep(1)    # yields to the loop instead of blocking\n    print(\"world\")\n\nasyncio.run(main())           # creates the loop, runs main, then closes it\n```\n\nCrucially this is **concurrency, not parallelism** — one thread, one core,\ninterleaving tasks at `await` points. Rule of thumb: asyncio shines when you\nhave **many tasks that spend most of their time waiting** on I\u002FO.\n",{"id":28,"difficulty":29,"q":30,"a":31},"async-def-await","medium","What do async def and await do?","**`async def`** defines a **coroutine function** — calling it doesn't run the\nbody, it returns a **coroutine object** that must be awaited or scheduled.\n**`await`** **suspends** the current coroutine until the awaited **awaitable**\n(another coroutine, a Task, or a Future) completes, handing control back to the\nevent loop in the meantime.\n\n```python\nimport asyncio\n\nasync def fetch():\n    await asyncio.sleep(1)    # suspension point — loop runs others here\n    return \"data\"\n\nasync def main():\n    coro = fetch()            # nothing has run yet\n    result = await coro       # now it runs; main suspends until it finishes\n    print(result)\n\nasyncio.run(main())\n```\n\nYou can only `await` **inside** an `async def`. Forgetting to await a coroutine\nis a common bug — it never runs and you get a \"coroutine was never awaited\"\nwarning. Think of `await` as \"pause me here and let others run until this is\nready.\"\n",{"id":33,"difficulty":14,"q":34,"a":35},"coroutines-vs-threads","How do coroutines differ from threads?","**Threads** use **preemptive** multitasking — the OS can switch threads at\n**any** point, so shared state needs locks and context switches are relatively\nexpensive. **Coroutines** use **cooperative** multitasking on **one thread** —\nswitches happen **only at explicit `await` points**, so the code between awaits\nis effectively atomic and switching is cheap.\n\n```python\nimport asyncio\n\nasync def task(name):\n    print(f\"{name} start\")\n    await asyncio.sleep(1)        # the ONLY place this can yield\n    print(f\"{name} done\")\n\nasync def main():\n    await asyncio.gather(task(\"a\"), task(\"b\"))   # thousands are feasible\n\nasyncio.run(main())\n```\n\nBecause there's no OS thread per task, you can run **tens of thousands** of\ncoroutines cheaply, and most data races disappear. The catch: a coroutine that\nnever awaits **monopolizes** the loop. Threads tolerate blocking code;\ncoroutines do not.\n",{"id":37,"difficulty":29,"q":38,"a":39},"asyncio-gather","How do you run coroutines concurrently with asyncio.gather?","Awaiting coroutines one by one runs them **sequentially**. To run them\n**concurrently**, schedule them together with **`asyncio.gather`** (or wrap each\nin a **Task**), which lets the loop interleave their `await` points.\n\n```python\nimport asyncio\n\nasync def fetch(n):\n    await asyncio.sleep(1)\n    return n * 2\n\nasync def main():\n    # all three overlap -> ~1 second total, not 3\n    results = await asyncio.gather(fetch(1), fetch(2), fetch(3))\n    print(results)        # [2, 4, 6] — order matches the arguments\n\nasyncio.run(main())\n```\n\n`gather` returns results in argument order and, by default, propagates the first\nexception. `asyncio.create_task(coro)` schedules a coroutine to start running\nimmediately so it overlaps with later code. The key idea: concurrency comes from\n**scheduling tasks together**, not from awaiting them in turn.\n",{"id":41,"difficulty":14,"q":42,"a":43},"dont-block-the-loop","What is the \"don't block the event loop\" rule?","Because asyncio runs on **one thread**, any code that **doesn't await** —\nheavy CPU work or **blocking synchronous I\u002FO** like `time.sleep`,\n`requests.get`, or blocking DB drivers — **freezes the entire loop**. Every other\ncoroutine stalls until that call returns.\n\n```python\nimport asyncio, time\n\nasync def bad():\n    time.sleep(5)              # BLOCKS the whole loop for 5s\n\nasync def good():\n    await asyncio.sleep(5)     # yields; other tasks keep running\n\n# offload unavoidable blocking\u002FCPU work to a thread or process pool:\nasync def offloaded():\n    loop = asyncio.get_running_loop()\n    await loop.run_in_executor(None, time.sleep, 5)   # runs in a thread\n```\n\nFixes: use **async-native libraries** (`aiohttp`, `asyncpg`), and push\nCPU-bound or unavoidably-blocking calls into `run_in_executor` (a thread pool, or\na process pool for CPU work). Rule of thumb: inside `async` code, never call\nsomething that blocks without awaiting it.\n",{"id":45,"difficulty":29,"q":46,"a":47},"when-async-helps","When does asyncio actually help?","asyncio wins for **high-concurrency I\u002FO-bound** workloads — thousands of\nnetwork calls, web requests, websocket connections, or database queries that\nspend their time **waiting**. While one request waits, the loop services others,\nso a single thread handles huge concurrency cheaply.\n\n```python\nimport asyncio\n\nasync def call_api(i):\n    await asyncio.sleep(0.5)        # stand-in for a network round trip\n    return i\n\nasync def main():\n    # 1000 \"requests\" overlap on one thread in ~0.5s of wall time\n    results = await asyncio.gather(*(call_api(i) for i in range(1000)))\n    print(len(results))             # 1000\n\nasyncio.run(main())\n```\n\nIt does **not** help **CPU-bound** work — that needs multiprocessing for real\nparallelism. And for a handful of blocking calls, plain threads are often\nsimpler. Reach for asyncio when concurrency is high and the bottleneck is\nwaiting on I\u002FO.\n",null,{"description":11},"Python interview questions on asyncio and async\u002Fawait: the event loop, coroutines vs threads, asyncio.gather, the don't-block-the-loop rule, and when async helps.","python\u002Fconcurrency\u002Fasyncio","asyncio & async\u002Fawait","Concurrency & Parallelism","concurrency","2026-06-18","Nmpm5dZjqwdFh49NpQFVIUo_4pc2dAboUlrFVlULydE",[58,62,65,66],{"subtopic":59,"path":60,"order":61},"Threading & the GIL","\u002Fpython\u002Fconcurrency\u002Fgil",1,{"subtopic":63,"path":64,"order":12},"Multiprocessing","\u002Fpython\u002Fconcurrency\u002Fmultiprocessing",{"subtopic":52,"path":21,"order":20},{"subtopic":67,"path":68,"order":69},"concurrent.futures","\u002Fpython\u002Fconcurrency\u002Fconcurrent-futures",4,1781808681459]