[{"data":1,"prerenderedAt":88},["ShallowReactive",2],{"qa-\u002Ffastapi\u002Fdependency-injection\u002Flifespan":3},{"page":4,"siblings":79,"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":74,"topic":75,"topicSlug":76,"updated":77,"__hash__":78},"qa\u002Ffastapi\u002Fdependency-injection\u002Flifespan.md","Lifespan",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","FastAPI","fastapi",{},true,3,"\u002Ffastapi\u002Fdependency-injection\u002Flifespan",[23,28,32,36,40,45,49,53,57,61,65],{"id":24,"difficulty":25,"q":26,"a":27},"lifespan-basics","easy","What is the `lifespan` parameter in FastAPI and what does it replace?","`lifespan` is an `async` context manager function that runs **startup** code\nbefore `yield` and **shutdown** code after it. It was introduced to replace\nthe older `@app.on_event(\"startup\")` \u002F `@app.on_event(\"shutdown\")` decorators,\nwhich are deprecated.\n\n```python\nfrom contextlib import asynccontextmanager\nfrom fastapi import FastAPI\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    # startup\n    await db_pool.connect()\n    print(\"DB connected\")\n    yield\n    # shutdown\n    await db_pool.disconnect()\n    print(\"DB disconnected\")\n\napp = FastAPI(lifespan=lifespan)\n```\n\nRule of thumb: always use `lifespan=` for new apps — it's the modern approach\nand groups startup\u002Fshutdown logic in one readable function.\n",{"id":29,"difficulty":25,"q":30,"a":31},"on-event-deprecated","Why are `@app.on_event(\"startup\")` decorators deprecated and what should you use instead?","The decorator approach had two problems:\n1. Startup and shutdown logic is split across two separate functions — harder\n   to read the resource lifecycle.\n2. You can't use `async with` or `yield`-based context managers in event handlers.\n\nThe `lifespan` context manager fixes both:\n\n```python\n# Old (deprecated)\n@app.on_event(\"startup\")\nasync def startup():\n    await cache.connect()\n\n@app.on_event(\"shutdown\")\nasync def shutdown():\n    await cache.disconnect()\n\n# New (preferred)\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    await cache.connect()\n    yield\n    await cache.disconnect()\n\napp = FastAPI(lifespan=lifespan)\n```\n\nRule of thumb: migrate `@app.on_event` to `lifespan` in any new or refactored\ncodebase — it's just moving the two functions into a context manager.\n",{"id":33,"difficulty":14,"q":34,"a":35},"app-state","How do you share objects (DB pool, HTTP client) across all requests in FastAPI?","Store them on `app.state` inside the `lifespan` function. `app.state` is a\n`Starlette` `State` object — a simple namespace you can attach anything to.\n\n```python\nimport httpx\nfrom contextlib import asynccontextmanager\nfrom fastapi import FastAPI, Request\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    app.state.http_client = httpx.AsyncClient()\n    yield\n    await app.state.http_client.aclose()\n\napp = FastAPI(lifespan=lifespan)\n\n@app.get(\"\u002Fproxy\")\nasync def proxy(request: Request):\n    client = request.app.state.http_client\n    resp = await client.get(\"https:\u002F\u002Fapi.example.com\u002Fdata\")\n    return resp.json()\n```\n\nRule of thumb: use `app.state` for truly shared singletons (connection pools,\nloaded ML models, config); use `Depends()` for per-request resources.\n",{"id":37,"difficulty":14,"q":38,"a":39},"lifespan-with-db","Show the standard pattern for initialising an async SQLAlchemy engine in lifespan.","```python\nfrom contextlib import asynccontextmanager\nfrom sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker\nfrom fastapi import FastAPI\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    engine = create_async_engine(settings.database_url, pool_size=10)\n    app.state.db_factory = async_sessionmaker(engine, expire_on_commit=False)\n    yield\n    await engine.dispose()\n\napp = FastAPI(lifespan=lifespan)\n\n# Dependency that uses the shared engine\nasync def get_db(request: Request):\n    async with request.app.state.db_factory() as session:\n        yield session\n```\n\nThe engine (and its connection pool) is created once at startup and disposed\ngracefully at shutdown.\n\nRule of thumb: create the engine in `lifespan`, not at module level — it lets\nyou inject different engines in tests via `lifespan` override.\n",{"id":41,"difficulty":42,"q":43,"a":44},"composing-lifespans","hard","How do you compose multiple startup\u002Fshutdown concerns without one giant lifespan function?","Nest `asynccontextmanager` functions inside the main lifespan:\n\n```python\nfrom contextlib import asynccontextmanager\n\n@asynccontextmanager\nasync def db_lifespan(app):\n    engine = create_async_engine(settings.db_url)\n    app.state.engine = engine\n    yield\n    await engine.dispose()\n\n@asynccontextmanager\nasync def cache_lifespan(app):\n    app.state.redis = Redis.from_url(settings.redis_url)\n    yield\n    await app.state.redis.aclose()\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    async with db_lifespan(app):\n        async with cache_lifespan(app):\n            yield\n\napp = FastAPI(lifespan=lifespan)\n```\n\nEach sub-lifespan owns its resource; teardown runs in reverse nesting order.\n\nRule of thumb: one context manager per resource — compose them in the main\n`lifespan` rather than putting everything into one giant function.\n",{"id":46,"difficulty":14,"q":47,"a":48},"lifespan-state-access","How do you access `app.state` from inside a dependency or middleware?","Three ways, depending on context:\n\n**In a dependency** — inject `Request`:\n```python\ndef get_client(request: Request) -> httpx.AsyncClient:\n    return request.app.state.http_client\n```\n\n**In middleware** — `request.app.state` is available the same way:\n```python\nasync def dispatch(self, request: Request, call_next):\n    client = request.app.state.http_client\n    ...\n```\n\n**At module level** — capture the `app` reference directly (only if in the\nsame module):\n```python\nclient = app.state.http_client   # only works after lifespan startup\n```\n\nRule of thumb: always access `app.state` through `request.app` in deps and\nmiddleware — it avoids circular imports and works correctly in tests with\n`TestClient`.\n",{"id":50,"difficulty":42,"q":51,"a":52},"lifespan-testing","How do you test lifespan events in FastAPI?","`TestClient` runs the full lifespan when used as a context manager:\n\n```python\nfrom fastapi.testclient import TestClient\nfrom app.main import app\n\ndef test_lifespan_sets_state():\n    with TestClient(app) as client:\n        # startup has run; app.state is populated\n        resp = client.get(\"\u002Fhealth\")\n        assert resp.status_code == 200\n    # shutdown has run; resources are closed\n```\n\nFor async tests with `httpx.AsyncClient`:\n```python\nfrom httpx import AsyncClient, ASGITransport\nimport pytest\n\n@pytest.mark.anyio\nasync def test_endpoint():\n    async with AsyncClient(\n        transport=ASGITransport(app=app), base_url=\"http:\u002F\u002Ftest\"\n    ) as client:\n        resp = await client.get(\"\u002Fitems\")\n        assert resp.status_code == 200\n```\n\nRule of thumb: always use `TestClient` as a context manager (`with TestClient(app)`)\nso startup and shutdown run — a non-context-manager `TestClient` skips them.\n",{"id":54,"difficulty":42,"q":55,"a":56},"lifespan-exception-handling","What happens if an exception is raised during startup in the `lifespan` function?","If an exception propagates out of the code before `yield`, FastAPI never starts\naccepting requests — the application exits. The teardown code (after `yield`)\ndoes **not** run because `yield` was never reached.\n\n```python\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    try:\n        await db.connect()        # if this raises, app won't start\n    except ConnectionError as e:\n        log.critical(\"DB unavailable: %s\", e)\n        raise                     # startup fails, process exits\n    yield\n    await db.disconnect()         # never reached if startup failed\n```\n\nFor resources that you want to clean up even if a later startup step fails,\nnest the `try\u002Ffinally` around each step individually.\n\nRule of thumb: let startup exceptions propagate — a crashed startup is\nbetter than a running app with a broken DB connection.\n",{"id":58,"difficulty":14,"q":59,"a":60},"router-vs-app-lifespan","Can `APIRouter` have its own lifespan?","No — `APIRouter` does not support `lifespan`. Only the root `FastAPI` instance\naccepts a lifespan function. To organise per-module startup logic, use helper\ncontext managers that the root `lifespan` composes:\n\n```python\n# routers\u002Fusers.py\n@asynccontextmanager\nasync def users_lifespan(app):\n    app.state.user_cache = {}\n    yield\n    app.state.user_cache.clear()\n\n# main.py\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    async with users_lifespan(app):\n        async with orders_lifespan(app):\n            yield\n\napp = FastAPI(lifespan=lifespan)\n```\n\nRule of thumb: keep the root `lifespan` as a compositor; define per-module\nstartup as `asynccontextmanager` functions and import them into main.\n",{"id":62,"difficulty":14,"q":63,"a":64},"background-startup","How do you run a background task continuously from application startup?","Use `anyio.create_task_group()` or `asyncio.create_task()` inside the lifespan:\n\n```python\nimport asyncio\nfrom contextlib import asynccontextmanager\n\nasync def poll_metrics():\n    while True:\n        await push_metrics_to_monitoring()\n        await asyncio.sleep(60)\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    task = asyncio.create_task(poll_metrics())\n    yield\n    task.cancel()\n    try:\n        await task\n    except asyncio.CancelledError:\n        pass\n```\n\nAlways cancel and await the task on shutdown — leaving it running causes\n\"Task was destroyed but it is pending!\" warnings in tests.\n\nRule of thumb: cancel background tasks explicitly in the shutdown phase;\nstore the `Task` handle on `app.state` if other parts of the app need to check it.\n",{"id":66,"difficulty":25,"q":67,"a":68},"request-state","What is `request.state` and how does it differ from `app.state`?","`request.state` is a per-request `State` object — separate for every HTTP\nrequest, discarded after the response is sent. `app.state` lives for the\nentire application lifetime.\n\n```python\n# Middleware sets per-request data\nclass RequestIDMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request: Request, call_next):\n        request.state.request_id = str(uuid4())\n        return await call_next(request)\n\n# Handler or dependency reads it\n@app.get(\"\u002Fitems\")\nasync def list_items(request: Request):\n    rid = request.state.request_id\n    return {\"request_id\": rid}\n```\n\nRule of thumb: use `request.state` for per-request metadata (trace IDs, auth\ncontext set by middleware); use `app.state` for shared singletons.\n",11,null,{"description":11},"FastAPI lifespan interview questions — asynccontextmanager, startup\u002Fshutdown, app.state, resource pooling and testing with lifespan.","fastapi\u002Fdependency-injection\u002Flifespan","Lifespan & App State","Dependency Injection","dependency-injection","2026-06-20","TvEk1ojPF6D2IHDvxb_Q10SSpFcZyCAfjEuwKc2-RqM",[80,84,87],{"subtopic":81,"path":82,"order":83},"Depends Basics","\u002Ffastapi\u002Fdependency-injection\u002Fdepends-basics",1,{"subtopic":85,"path":86,"order":12},"Advanced Dependencies","\u002Ffastapi\u002Fdependency-injection\u002Fadvanced-deps",{"subtopic":74,"path":21,"order":20},1782244112973]