[{"data":1,"prerenderedAt":87},["ShallowReactive",2],{"qa-\u002Ffastapi\u002Fdeployment\u002Fbackground-tasks":3},{"page":4,"siblings":78,"blog":70},{"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,"questionsCount":69,"related":70,"seo":71,"seoDescription":72,"stem":73,"subtopic":6,"topic":74,"topicSlug":75,"updated":76,"__hash__":77},"qa\u002Ffastapi\u002Fdeployment\u002Fbackground-tasks.md","Background Tasks",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","FastAPI","fastapi",{},true,3,"\u002Ffastapi\u002Fdeployment\u002Fbackground-tasks",[23,28,32,36,40,44,49,53,57,61,65],{"id":24,"difficulty":25,"q":26,"a":27},"background-tasks-basics","easy","What are `BackgroundTasks` in FastAPI and how do you use them?","`BackgroundTasks` lets you schedule functions to run **after the HTTP response\nis sent** to the client. The handler returns immediately; the task runs in the\nsame process after the response is fully written.\n\n```python\nfrom fastapi import BackgroundTasks, FastAPI\n\napp = FastAPI()\n\ndef write_log(message: str):\n    with open(\"log.txt\", \"a\") as f:\n        f.write(message + \"\\n\")\n\n@app.post(\"\u002Fitems\")\nasync def create_item(item: Item, background_tasks: BackgroundTasks):\n    result = await db.save(item)\n    background_tasks.add_task(write_log, f\"Created item {result.id}\")\n    return result   # response sent; write_log runs after\n```\n\nRule of thumb: inject `BackgroundTasks` as a parameter — FastAPI provides it\nautomatically; never instantiate it yourself.\n",{"id":29,"difficulty":14,"q":30,"a":31},"async-background-task","Can background tasks in FastAPI be async functions?","Yes — `add_task` accepts both `async def` and regular `def` functions.\n\n```python\nasync def send_welcome_email(email: str):\n    await smtp_client.send(email, \"Welcome!\")\n\ndef sync_audit_log(event: str):\n    logger.info(event)    # sync — runs in thread pool\n\n@app.post(\"\u002Fsignup\")\nasync def signup(user: UserCreate, background_tasks: BackgroundTasks):\n    new_user = await db.create(user)\n    background_tasks.add_task(send_welcome_email, new_user.email)   # async\n    background_tasks.add_task(sync_audit_log, f\"signup:{new_user.id}\")  # sync\n    return new_user\n```\n\nAsync tasks run on the event loop; sync tasks run in the thread pool.\nTasks execute **sequentially** in the order they were added.\n\nRule of thumb: prefer async background tasks in an async app — they don't\nblock the event loop and don't require thread pool slots.\n",{"id":33,"difficulty":14,"q":34,"a":35},"background-task-ordering","In what order do multiple `BackgroundTasks` run?","**Sequentially, in the order they were added.** If task A runs 2 seconds and\ntask B runs 1 second, total background time is 3 seconds.\n\n```python\nbackground_tasks.add_task(task_a)   # runs first\nbackground_tasks.add_task(task_b)   # runs second, after task_a completes\n```\n\nFor parallel background work, use `asyncio.gather()` inside a single async task:\n\n```python\nasync def parallel_tasks(email: str, item_id: int):\n    await asyncio.gather(\n        send_email(email),\n        update_analytics(item_id),\n    )\n\nbackground_tasks.add_task(parallel_tasks, user.email, item.id)\n```\n\nRule of thumb: if background tasks are independent and their combined duration\nmatters, wrap them in an `asyncio.gather` inside a single background task.\n",{"id":37,"difficulty":14,"q":38,"a":39},"background-task-error-handling","What happens if a background task raises an exception in FastAPI?","The exception is logged (Uvicorn logs it as an unhandled exception) but\n**does not affect the response** — it was already sent. The client never\nknows the task failed.\n\n```python\ndef risky_task():\n    raise ValueError(\"Something went wrong\")   # logged, response already sent\n\n@app.post(\"\u002Fitems\")\nasync def create_item(item: Item, background_tasks: BackgroundTasks):\n    background_tasks.add_task(risky_task)\n    return item   # this 200 response is already sent before risky_task runs\n```\n\nFor reliable task execution (retries, persistence), use a proper task queue\n(Celery, ARQ, RQ) where tasks can be retried on failure.\n\nRule of thumb: use `BackgroundTasks` only for best-effort work (email\nnotification, audit logging); never for work that must succeed (payment processing,\ndata integrity).\n",{"id":41,"difficulty":14,"q":42,"a":43},"background-task-vs-queue","When should you use `BackgroundTasks` vs a proper task queue like Celery?","| Concern | BackgroundTasks | Task queue (Celery\u002FARQ) |\n|---------|----------------|------------------------|\n| Must succeed | ❌ no retries | ✅ configurable retries |\n| Survives crash\u002Frestart | ❌ lost if process dies | ✅ persisted in broker |\n| Long-running (minutes) | ❌ blocks worker | ✅ separate worker |\n| Rate-limited external API | ❌ no throttling | ✅ rate limiting |\n| Monitor\u002Finspect tasks | ❌ | ✅ Flower, ARQ dashboard |\n| Simple fire-and-forget | ✅ | overkill |\n\n```python\n# BackgroundTasks — fine for quick, best-effort\nbackground_tasks.add_task(send_analytics_event, event_data)\n\n# Celery — required for reliability\nprocess_payment.delay(order_id)   # persisted in Redis\u002FRabbitMQ\n```\n\nRule of thumb: if the task must succeed, use a task queue; if losing it\noccasionally is acceptable, `BackgroundTasks` is simpler.\n",{"id":45,"difficulty":46,"q":47,"a":48},"celery-fastapi","hard","How do you integrate Celery with a FastAPI application?","```python\n# celery_app.py\nfrom celery import Celery\n\ncelery = Celery(\n    \"myapp\",\n    broker=\"redis:\u002F\u002Flocalhost:6379\u002F0\",\n    backend=\"redis:\u002F\u002Flocalhost:6379\u002F1\",\n)\n\n@celery.task\ndef send_email_task(to: str, subject: str, body: str):\n    smtp.send(to, subject, body)\n```\n\n```python\n# FastAPI route\nfrom celery_app import send_email_task\n\n@app.post(\"\u002Fsignup\")\nasync def signup(user: UserCreate):\n    new_user = await db.create(user)\n    send_email_task.delay(new_user.email, \"Welcome!\", \"...\")\n    return new_user\n```\n\nStart workers separately:\n```bash\ncelery -A celery_app worker --loglevel=info\n```\n\nRule of thumb: keep Celery tasks in a separate module from FastAPI routes —\ntasks should be importable by Celery workers that don't start the web server.\n",{"id":50,"difficulty":46,"q":51,"a":52},"arq-fastapi","What is ARQ and why might you prefer it over Celery for a FastAPI app?","**ARQ** (Async Redis Queue) is a task queue built for asyncio — tasks are `async def`\nfunctions, the worker is an event loop, and it uses Redis as the broker.\n\n```python\n# tasks.py\nfrom arq import cron\n\nasync def send_welcome_email(ctx, email: str):\n    await ctx[\"smtp\"].send(email, \"Welcome!\")\n\nclass WorkerSettings:\n    functions = [send_welcome_email]\n    redis_settings = RedisSettings()\n```\n\n```python\n# FastAPI\nfrom arq import create_pool\n\n@asynccontextmanager\nasync def lifespan(app):\n    app.state.arq = await create_pool(RedisSettings())\n    yield\n    await app.state.arq.aclose()\n\n@app.post(\"\u002Fsignup\")\nasync def signup(user: UserCreate, request: Request):\n    await request.app.state.arq.enqueue_job(\"send_welcome_email\", user.email)\n    return {\"status\": \"created\"}\n```\n\nARQ advantages over Celery for async apps: native async, simpler setup,\nno serialisation overhead for Python objects, type hints work naturally.\n\nRule of thumb: choose ARQ for async-first apps; choose Celery when you need\nmature monitoring (Flower), scheduled tasks (Celery Beat), or a non-Python worker ecosystem.\n",{"id":54,"difficulty":46,"q":55,"a":56},"background-task-db-session","How do you use a database session inside a FastAPI background task?","Background tasks run **after the request session is closed** — you cannot reuse\nthe request's DB session. Create a new session inside the task:\n\n```python\nfrom sqlalchemy.orm import Session\nfrom app.db import SessionLocal\n\ndef send_order_confirmation(order_id: int):\n    db = SessionLocal()\n    try:\n        order = db.get(Order, order_id)\n        send_email(order.user.email, f\"Order {order_id} confirmed\")\n        db.commit()\n    finally:\n        db.close()\n\n@app.post(\"\u002Forders\")\nasync def create_order(data: OrderCreate, background_tasks: BackgroundTasks):\n    order = await db.create(data)\n    background_tasks.add_task(send_order_confirmation, order.id)\n    return order\n```\n\nPass the **ID**, not the ORM object — ORM objects are tied to a session; they\ncan't be used in a different session.\n\nRule of thumb: always open a fresh DB session inside background tasks; never\npass ORM objects from the request session into background tasks.\n",{"id":58,"difficulty":46,"q":59,"a":60},"periodic-tasks","How do you run a periodic\u002Fscheduled task in FastAPI?","**Option A — APScheduler** (in-process):\n```python\nfrom apscheduler.schedulers.asyncio import AsyncIOScheduler\nfrom contextlib import asynccontextmanager\n\n@asynccontextmanager\nasync def lifespan(app):\n    scheduler = AsyncIOScheduler()\n    scheduler.add_job(cleanup_expired_tokens, \"interval\", minutes=30)\n    scheduler.start()\n    yield\n    scheduler.shutdown()\n\napp = FastAPI(lifespan=lifespan)\n```\n\n**Option B — ARQ cron jobs**:\n```python\nclass WorkerSettings:\n    cron_jobs = [\n        cron(cleanup_expired_tokens, minute={0, 30}),  # every 30 min\n    ]\n```\n\n**Option C — External scheduler** (cron, Kubernetes CronJob, AWS EventBridge):\nHits a `\u002Finternal\u002Fcleanup` endpoint at the scheduled time.\n\nRule of thumb: in-process schedulers (APScheduler) are convenient but tasks\nrun on every pod in a scaled deployment; use a single external scheduler or a\ndistributed lock to prevent duplicate execution.\n",{"id":62,"difficulty":14,"q":63,"a":64},"task-result","How do you return a task ID and let clients poll for the result in FastAPI?","The async job pattern: accept the request, enqueue the task, return a task ID;\nprovide a polling endpoint.\n\n```python\nimport uuid\nfrom arq import create_pool\n\n@app.post(\"\u002Freports\", status_code=202)\nasync def generate_report(request: Request, params: ReportParams):\n    task_id = str(uuid.uuid4())\n    await request.app.state.arq.enqueue_job(\"build_report\", task_id, params.dict())\n    return {\"task_id\": task_id, \"status\": \"pending\"}\n\n@app.get(\"\u002Freports\u002F{task_id}\")\nasync def get_report_status(task_id: str, request: Request):\n    job = await request.app.state.arq.job(task_id)\n    if job is None:\n        raise HTTPException(404)\n    status = await job.status()\n    if status == \"complete\":\n        result = await job.result()\n        return {\"status\": \"complete\", \"result\": result}\n    return {\"status\": status}\n```\n\nRule of thumb: return **202 Accepted** for async tasks — it signals that the\nrequest was accepted but processing isn't complete yet.\n",{"id":66,"difficulty":46,"q":67,"a":68},"background-task-memory-leak","What is a common memory leak pattern with FastAPI background tasks?","Capturing large objects in the background task's closure keeps them alive until\nthe task completes — even after the request is done.\n\n```python\n# BAD — the entire response object stays in memory during task\n@app.post(\"\u002Fitems\")\nasync def create_item(item: Item, background_tasks: BackgroundTasks):\n    all_items = await db.get_all_items()   # large list\n    background_tasks.add_task(process_items, all_items)  # captured in closure\n    return item\n\n# GOOD — pass just the IDs; let the task load what it needs\n@app.post(\"\u002Fitems\")\nasync def create_item(item: Item, background_tasks: BackgroundTasks):\n    new_item = await db.save(item)\n    background_tasks.add_task(process_new_item, new_item.id)  # just an int\n    return new_item\n```\n\nRule of thumb: pass primitive values (IDs, strings) to background tasks —\nnot large ORM results, response objects, or request streams.\n",11,null,{"description":11},"FastAPI background task interview questions — BackgroundTasks, task queues, Celery, ARQ, when to use each and common pitfalls.","fastapi\u002Fdeployment\u002Fbackground-tasks","Deployment & Middleware","deployment","2026-06-20","-WcStUf5UsPUsZy-rQs45Aes9QrVEJgM5dHfqle9Ltg",[79,83,86],{"subtopic":80,"path":81,"order":82},"Uvicorn & Gunicorn","\u002Ffastapi\u002Fdeployment\u002Fuvicorn-gunicorn",1,{"subtopic":84,"path":85,"order":12},"Middleware","\u002Ffastapi\u002Fdeployment\u002Fmiddleware",{"subtopic":6,"path":21,"order":20},1782244114876]