Interview frequency: …
Short answer
A coroutine is a description of asynchronous work that does not run until it is awaited or scheduled, a Task wraps a coroutine and schedules it to run concurrently on the loop, and a Future is a low-level object representing a result that will be completed later (a Task is a subtype of it).
Explanation
await coro() runs the coroutine sequentially, whereas asyncio.create_task(coro()) starts running it in the background immediately. Keep a reference to the Task; the loop keeps only a weak reference, so the task may be garbage-collected before it completes, and exceptions in tasks nobody awaits get lost. The best way to manage task lifecycles is TaskGroup.
Code example
import asyncio
background_tasks: set[asyncio.Task] = set()
def fire_and_forget(coro) -> None:
task = asyncio.create_task(coro)
background_tasks.add(task) # Prevents the task from being collected too early
task.add_done_callback(background_tasks.discard)Common mistakes
- Calling create_task without keeping the reference, so the task or its errors vanish.
- Awaiting tasks one after another right after creating them, which cancels concurrency.