time.sleep vs asyncio.sleep: blocking the event loop

Read the explanation
What if

slow_call uses time.sleep inside a coroutine. It blocks the loop's only thread, so the ticker can't tick until it ends.

Predict first

The ticker prints every 20 ms. While slow_call runs time.sleep(0.1), how many ticks print?

0123456789tick 1ticker: await asyncio.sleepsleepready, but the loop is blockedtick 2sleepticks 3-5slow_call: print startsstartstime.sleep: blocks the loopprint ends

Tap any block to see what that thread is doing.

  • Running
  • Waiting / parked
Step 1/5

One thread runs both tasks. The ticker prints, then awaits its 20 ms sleep.

1async def ticker():2    for i in range(1, 6):3        print(f"tick {i}")4        await asyncio.sleep(0.02)5 6async def slow_call():7    print("slow call starts")8    time.sleep(0.1)  # blocks the event loop's only thread9    print("slow call ends")10 11async def main():12    await asyncio.gather(ticker(), slow_call())13 14if __name__ == "__main__":15    asyncio.run(main())

Explanation

What just happened

Both tasks run on one thread, the one that called asyncio.run. slow_call used time.sleep, which doesn’t await: it put the whole thread to sleep. The ticker’s 20 ms were up, but the loop was stuck, so tick 2 came only after slow call ends.

Try the what-if switch

Why

asyncio is cooperative. A task runs until it reaches an await that has to wait, and only then can the loop run another task. A blocking call never reaches one, so it holds the thread for everyone.

Watch out

Blocking calls don’t look different from any other function call: time.sleep, requests.get and reading a big file all block. Inside async def, use the async version or asyncio.to_thread.

Common questions

What's the difference between time.sleep and asyncio.sleep?

time.sleep blocks the thread, and in asyncio that's the event loop's only thread, so no task runs. await asyncio.sleep pauses just this task and lets the loop run others.

How do I call blocking code from asyncio?

Wrap it in await asyncio.to_thread(func, args). It runs on a worker thread from the loop's default thread pool, and the loop stays free.

Do asyncio tasks run on different threads?

No. Every task runs on the thread that called asyncio.run, one at a time. Tasks switch only at an await.

Verified: this timeline matches a real program, checked in CI on Python 3.14, 3.15 and free-threaded 3.15t. How we verify. Unclear or wrong? Tell us