Fixing 'RuntimeError: no running event loop' in Python 3.10, 3.11, and 3.12

intermediate🐍 Python2026-07-11| Python 3.10, 3.11, 3.12, or newer versions on Windows, Linux, or macOS.

Error Message

RuntimeError: no running event loop
#python#asyncio#event-loop#concurrency#debugging

Why Your Async Code Suddenly BrokeUpgrading to Python 3.10 or 3.11 often feels like a win until your asynchronous scripts suddenly crash. Code that ran perfectly for years on Python 3.8 might now throw a stubborn error message:

RuntimeError: no running event loop

This happens because Python changed how it handles the underlying 'engine' of async code. In older versions, calling asyncio.get_event_loop() would silently create a new loop if one wasn't running. Starting with Python 3.10, the developers tightened the rules. Now, the function expects a loop to already exist. If it doesn't find one, it gives up and throws this exception.

The Common ScenarioYou’ll likely run into this when following older tutorials or maintaining legacy scripts. It frequently pops up when integrating asyncio with libraries like aiohttp or discord.py outside of their standard setup. Here is the classic pattern that fails in modern Python:

import asyncio

async def my_task():
    print("Task is running")
    await asyncio.sleep(1)

# This worked in 2019, but fails in Python 3.10+
loop = asyncio.get_event_loop()
loop.run_until_complete(my_task())

The Logic Behind the ChangeMaintainers deprecated implicit loop creation to prevent messy resource leaks. In complex multi-threaded apps, having loops spin up automatically created unpredictable bugs that were a nightmare to debug. By forcing you to be explicit, Python ensures your code is more stable and easier to scale. Since the release of Python 3.10.0 in late 2021, this has become the new standard for the ecosystem.

The Best Solution: Use asyncio.run()The cleanest fix is to stop managing the loop yourself. Python 3.7 introduced asyncio.run(), which has been the gold standard for over five years. It handles the heavy lifting—creating the loop, running your code, and cleaning up afterward—in a single line.

The Modern Way:```

import asyncio

async def main(): print("Task is running") await asyncio.sleep(1)

if name == "main": # This is the safe, modern approach asyncio.run(main())


`asyncio.run()` is robust. It ensures a fresh loop is active before your tasks start and shuts it down properly when they finish. This prevents the 'no running event loop' error entirely.
## Handling Complex or Multi-threaded ProjectsStandard fixes aren't always enough for low-level networking or custom thread management. If your architecture requires a manual loop object, you must initialize it explicitly using the event loop policy.
### Manual InitializationIf you absolutely need a `loop` reference at the top level, follow this explicit pattern:

import asyncio

async def main(): print("Executing in a manually set loop")

1. Manually create the loop

loop = asyncio.new_event_loop()

2. Assign it to the current thread

asyncio.set_event_loop(loop)

try: loop.run_until_complete(main()) finally: # 3. Always close the loop to prevent memory leaks loop.close()


### Fixing Errors in ThreadsEvent loops are thread-local. If you call `asyncio.get_event_loop()` inside a `threading.Thread`, it will fail because the main thread's loop is invisible there. You must give each thread its own engine:

import asyncio import threading

def thread_worker(): # Each thread needs its own loop setup new_loop = asyncio.new_event_loop() asyncio.set_event_loop(new_loop)

new_loop.run_until_complete(asyncio.sleep(1))
print("Thread task finished")
new_loop.close()

Launching 3 worker threads

threads = [threading.Thread(target=thread_worker) for _ in range(3)] for t in threads: t.start() for t in threads: t.join()


## What About Jupyter Notebooks?Jupyter and IPython are special cases. They run their own event loop in the background to handle interactive cells. If you try to use `asyncio.run()` there, you'll actually get a different error saying a loop is already running.
The fix is simple: just `await` your function directly in the cell.

Inside a Jupyter cell

import asyncio

async def my_task(): await asyncio.sleep(1) print("Done")

No need for asyncio.run()

await my_task()


## Quick Verification ChecklistTo make sure your fix sticks, run through these four checks:

  - **Verify Version:** Confirm you are on Python 3.10+ by running `python --version`.
  - **Test Entry Points:** Ensure all async execution starts from a single `asyncio.run()` call if possible.
  - **Cleanup Check:** If you used `new_event_loop()`, verify that `loop.is_closed()` is `True` after the script ends.
  - **Thread Isolation:** If using multiple threads, use `id(asyncio.get_event_loop())` to confirm each thread has a unique loop ID.
By moving away from `get_event_loop()`, you aren't just fixing a bug. You're future-proofing your code for Python 3.13 and beyond.

Related Error Notes