Python asyncio unreferenced tasks are destroyed by garbage collector?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Yes, an asyncio task without a strong reference can disappear before you intended, which is why Python warns about tasks being destroyed while still pending. Creating a task schedules it on the event loop, but scheduling work is not the same as owning that task's lifetime.
Why the Problem Happens
asyncio.create_task() returns a Task object. If your code immediately drops that object and nothing else keeps a strong reference, the task may be garbage collected while it is still pending.
That is the point behind warnings such as:
Task was destroyed but it is pending!
The event loop is running the task, but your application still needs to manage task ownership if the work is meant to outlive the current call site.
Fragile "Fire and Forget" Example
This code looks harmless but is risky because the task object is discarded right away:
Sometimes this appears to work. Sometimes it triggers warnings or loses work. The problem is not the coroutine itself. The problem is that nothing in your application is responsible for the task after creation.
Keep a Strong Reference
The common pattern is to store background tasks in a set and remove them when they finish.
This makes task lifetime explicit. The set keeps the task alive, and the done callback removes it when it is no longer needed.
Prefer Structured Concurrency When Possible
In many cases, the best answer is not unmanaged background tasks at all. If the result matters, await it directly. If several tasks belong together, run them under a structured mechanism such as asyncio.gather or TaskGroup.
Structured concurrency gives the tasks an owning scope, which is usually easier to reason about than loose background work.
Background Tasks Still Need Shutdown Logic
Keeping a strong reference solves only one problem. Long-lived background tasks also need clear cancellation and shutdown behavior. If a service creates background tasks for metrics, logging, or message flushing, the application should cancel or await those tasks during shutdown rather than hoping the interpreter exit sequence handles it cleanly.
That is why "fire and forget" is often a misleading phrase in async programs. The task might be detached from one function, but it still needs an owner somewhere.
Common Pitfalls
Assuming asyncio.create_task() alone guarantees safe task lifetime is the main mistake behind this issue.
Creating a background task and then dropping the returned object means your code has no durable ownership of the work.
Treating fire-and-forget tasks as exempt from cancellation and shutdown logic usually produces warnings or lost work later.
Using unmanaged background tasks when a direct await, gather, or TaskGroup would be simpler creates avoidable complexity.
Keeping references forever without removing completed tasks can grow memory usage unnecessarily.
Summary
- Unreferenced
asynciotasks can be garbage collected while still pending. - Keep a strong reference if a background task must continue independently.
- A task set with a done callback is a common management pattern.
- Prefer structured tools such as
gatherorTaskGroupwhen tasks belong to one scope. - Scheduling a task is not the same as managing its lifetime and shutdown correctly.

