[{"data":1,"prerenderedAt":108},["ShallowReactive",2],{"qa-\u002Ffastapi\u002Ffundamentals\u002Ftype-hints":3},{"page":4,"siblings":91,"blog":82},{"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":81,"related":82,"seo":83,"seoDescription":84,"stem":85,"subtopic":86,"topic":87,"topicSlug":88,"updated":89,"__hash__":90},"qa\u002Ffastapi\u002Ffundamentals\u002Ftype-hints.md","Type Hints",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"easy","md","FastAPI","fastapi",{},true,4,"\u002Ffastapi\u002Ffundamentals\u002Ftype-hints",[23,27,31,36,40,44,48,52,56,60,64,68,72,77],{"id":24,"difficulty":14,"q":25,"a":26},"why-type-hints","Why are Python type hints central to how FastAPI works?","FastAPI uses **runtime type inspection** (via `typing`, `inspect` and Pydantic)\nto derive three things from your function signatures automatically:\n\n1. **Where** each parameter comes from (path, query, body, header).\n2. **How** to parse and validate the incoming value (int, str, Pydantic model).\n3. **What** the OpenAPI schema should look like.\n\n```python\n@app.get(\"\u002Fitems\u002F{item_id}\")\nasync def get_item(\n    item_id: int,          # → path int, validated, documented as integer\n    q: str | None = None,  # → optional query string, documented\n    item: Item,            # → JSON body parsed into Pydantic model\n):\n    ...\n```\n\nWithout type hints, FastAPI cannot infer any of this — parameters become\nuntyped strings and no schema is generated.\n\nRule of thumb: in FastAPI, type annotations are not optional documentation —\nthey are the mechanism that drives parsing, validation, and the API contract.\n",{"id":28,"difficulty":14,"q":29,"a":30},"optional-parameters","How do you declare an optional query parameter in FastAPI?","Give the parameter a default value of `None` and annotate it with\n`str | None` (Python 3.10+) or `Optional[str]` (Python 3.9-).\n\n```python\nfrom typing import Optional\n\n@app.get(\"\u002Fitems\")\nasync def list_items(\n    q: str | None = None,          # optional, defaults to None\n    limit: int = 10,               # optional with a non-None default\n    offset: int = 0,\n):\n    ...\n```\n\nIn the OpenAPI schema, parameters with defaults appear as `required: false`.\nFastAPI only marks a query param `required: true` when there is **no default**.\n\nRule of thumb: `= None` makes any parameter optional; omit the default to make\nit required.\n",{"id":32,"difficulty":33,"q":34,"a":35},"annotated","medium","What is `Annotated` and how does FastAPI use it?","`Annotated[T, metadata]` (from `typing`) attaches extra metadata to a type\nwithout changing the type itself. FastAPI reads the metadata to configure\nparameter behaviour — Query constraints, Body options, Depends() etc.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query, Path\n\n@app.get(\"\u002Fitems\u002F{item_id}\")\nasync def get_item(\n    item_id: Annotated[int, Path(ge=1)],          # path param, must be >= 1\n    q: Annotated[str | None, Query(max_length=50)] = None,\n):\n    ...\n```\n\nThis keeps type information separate from validation metadata and works better\nwith type checkers (mypy, pyright) than the old `item_id: int = Path(ge=1)` style.\n\nRule of thumb: prefer `Annotated[T, Field(...)]` style in new code — it separates\nthe type from the constraint and is more explicit about what each annotation means.\n",{"id":37,"difficulty":33,"q":38,"a":39},"enum-annotation","How do you restrict a parameter to a fixed set of values using an Enum?","Annotate the parameter with a `str` (or `int`) Enum subclass. FastAPI validates\nthe incoming value against the enum members and documents the allowed values in\nOpenAPI.\n\n```python\nfrom enum import Enum\n\nclass Size(str, Enum):\n    small = \"small\"\n    medium = \"medium\"\n    large = \"large\"\n\n@app.get(\"\u002Fitems\")\nasync def list_items(size: Size = Size.medium):\n    return {\"size\": size.value}\n```\n\nInheriting from both `str` and `Enum` ensures the value is JSON-serialisable\nas a string directly.\n\nRule of thumb: use `str` enums for path\u002Fquery params, `int` enums for numeric\ncodes; always inherit from `str`\u002F`int` so serialisation is automatic.\n",{"id":41,"difficulty":33,"q":42,"a":43},"list-query-param","How do you accept a list of values from a query parameter?","Annotate with `list[str]` (or `List[str]`) and use `Query()` to tell FastAPI\nto collect multiple values for the same key.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query\n\n@app.get(\"\u002Fitems\")\nasync def list_items(\n    tags: Annotated[list[str], Query()] = [],\n):\n    return {\"tags\": tags}\n# GET \u002Fitems?tags=python&tags=fastapi → {\"tags\": [\"python\", \"fastapi\"]}\n```\n\nWithout `Query()`, a bare `list[str]` annotation would be treated as a body\nparameter by FastAPI.\n\nRule of thumb: always wrap `list[T]` query params with `Query()` to signal that\nmultiple same-key values should be collected into a list.\n",{"id":45,"difficulty":14,"q":46,"a":47},"header-parameter","How do you read a request header in FastAPI?","Annotate the parameter with `Header()`. FastAPI automatically converts the\nparameter name from snake_case to the HTTP header's hyphen-case format.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Header\n\n@app.get(\"\u002Fitems\")\nasync def list_items(\n    x_token: Annotated[str | None, Header()] = None,\n):\n    # reads the \"X-Token\" HTTP header\n    return {\"token\": x_token}\n```\n\nTo read a header with underscores in the name (non-standard), pass\n`convert_underscores=False` to `Header()`.\n\nRule of thumb: FastAPI converts `_` → `-` automatically for headers; use\n`convert_underscores=False` only for custom non-standard header names.\n",{"id":49,"difficulty":14,"q":50,"a":51},"cookie-parameter","How do you read a cookie value in a FastAPI handler?","Annotate the parameter with `Cookie()`.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Cookie\n\n@app.get(\"\u002Fme\")\nasync def current_user(\n    session_id: Annotated[str | None, Cookie()] = None,\n):\n    if not session_id:\n        raise HTTPException(status_code=401)\n    return {\"session\": session_id}\n```\n\nCookies are accessible only via `Cookie()` — they won't be picked up as query\nor path params. To set a cookie in the response, inject `Response` and call\n`response.set_cookie(key, value)`.\n\nRule of thumb: read cookies with `Cookie()`, write them with `response.set_cookie()`\ninjected via the `Response` parameter.\n",{"id":53,"difficulty":33,"q":54,"a":55},"pydantic-field-in-handler","How do you add validation constraints (min\u002Fmax, regex) to a query parameter?","Use `Query()` with constraint kwargs inside `Annotated`. These map to Pydantic\nfield constraints under the hood.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query\n\n@app.get(\"\u002Fitems\")\nasync def search(\n    q: Annotated[str, Query(min_length=3, max_length=100, pattern=r\"^\\w+$\")],\n    page: Annotated[int, Query(ge=1, le=1000)] = 1,\n):\n    ...\n```\n\nConstraints are reflected in the OpenAPI schema so clients can validate before\nsending.\n\nRule of thumb: put constraint kwargs in `Query()`\u002F`Path()`\u002F`Body()` — never\nvalidate manually in the handler body for data that comes from the request.\n",{"id":57,"difficulty":33,"q":58,"a":59},"body-singular-field","How do you accept a single non-model value in the request body?","Use `Body()` annotation. Without it, FastAPI treats simple types as query params.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Body\n\n@app.post(\"\u002Fitems\u002F{id}\u002Ftag\")\nasync def add_tag(\n    id: int,\n    tag: Annotated[str, Body()],   # reads {\"tag\": \"python\"} or just \"python\" with embed=False\n):\n    ...\n```\n\nFor a single-field body that must be wrapped in a key:\n```python\ntag: Annotated[str, Body(embed=True)]   # expects {\"tag\": \"value\"}\n```\n\nRule of thumb: use a Pydantic model whenever you have multiple body fields;\nuse `Body()` only for a single primitive value that doesn't warrant a model.\n",{"id":61,"difficulty":33,"q":62,"a":63},"form-data","How do you accept form data (not JSON) in FastAPI?","Use `Form()` instead of `Body()`. Install `python-multipart` first.\n\n```python\nfrom fastapi import Form\n\n@app.post(\"\u002Flogin\")\nasync def login(\n    username: Annotated[str, Form()],\n    password: Annotated[str, Form()],\n):\n    ...\n```\n\nNote: **you cannot mix JSON body and `Form()` fields in the same handler** —\nthe `Content-Type` is either `application\u002Fjson` or `application\u002Fx-www-form-urlencoded`\u002F\n`multipart\u002Fform-data`, not both.\n\nRule of thumb: use `Form()` for HTML form submissions or OAuth2 password flows;\nuse JSON body for REST API calls.\n",{"id":65,"difficulty":33,"q":66,"a":67},"file-upload","How do you accept a file upload in FastAPI?","Use `UploadFile` for streaming (recommended) or `bytes` for small in-memory files.\n\n```python\nfrom fastapi import UploadFile, File\n\n@app.post(\"\u002Fupload\")\nasync def upload(file: UploadFile):\n    content = await file.read()\n    return {\"filename\": file.filename, \"size\": len(content)}\n```\n\n`UploadFile` attributes: `filename`, `content_type`, `file` (SpooledTemporaryFile).\nFor multiple files: `files: list[UploadFile]`.\n\nAlways `await file.read()` — the file object is async. For large files, read in\nchunks to avoid loading the whole thing into memory:\n\n```python\nwhile chunk := await file.read(8192):\n    process(chunk)\n```\n\nRule of thumb: prefer `UploadFile` over `bytes` — it streams large files without\nloading them into memory; `bytes` is fine only for small known-size uploads.\n",{"id":69,"difficulty":33,"q":70,"a":71},"response-model-exclude-none","How do you exclude `None` fields from a response in FastAPI?","Set `response_model_exclude_none=True` in the route decorator. FastAPI calls\n`model_dump(exclude_none=True)` on the Pydantic response model before serialising.\n\n```python\nclass UserOut(BaseModel):\n    id: int\n    name: str\n    bio: str | None = None\n\n@app.get(\"\u002Fusers\u002F{id}\", response_model=UserOut, response_model_exclude_none=True)\nasync def get_user(id: int):\n    return {\"id\": 1, \"name\": \"Alice\"}\n    # bio is None → omitted from response: {\"id\": 1, \"name\": \"Alice\"}\n```\n\nRelated flags: `response_model_exclude_unset=True` (skip fields not explicitly set\nby the handler) and `response_model_exclude={\"internal_field\"}` (always strip specific fields).\n\nRule of thumb: use `exclude_none=True` for sparse models where `None` means\n\"not present\"; use `exclude_unset=True` when you want PATCH-style partial output.\n",{"id":73,"difficulty":74,"q":75,"a":76},"union-response","hard","How do you type a route that can return one of two Pydantic models?","Use `Union[ModelA, ModelB]` (or `ModelA | ModelB`) as the `response_model`.\nFastAPI includes both schemas in the OpenAPI spec.\n\n```python\nfrom typing import Union\n\nclass Cat(BaseModel): name: str; meows: bool\nclass Dog(BaseModel): name: str; barks: bool\n\n@app.get(\"\u002Fpet\u002F{id}\", response_model=Union[Cat, Dog])\nasync def get_pet(id: int) -> Cat | Dog:\n    pet = await db.get_pet(id)\n    return pet   # FastAPI picks the matching model for serialisation\n```\n\nFastAPI serialises using the **first matching model** from left to right in the\nUnion. For discriminated unions, use Pydantic's `discriminator` field for\nunambiguous selection.\n\nRule of thumb: discriminated unions (with a `type: Literal[\"cat\"]` field) are\ncleaner than bare `Union` — they document intent and avoid ambiguous serialisation.\n",{"id":78,"difficulty":74,"q":79,"a":80},"generic-response","How do you create a generic paginated response type in FastAPI?","Use `Generic[T]` from `typing` with a Pydantic `BaseModel`:\n\n```python\nfrom typing import Generic, TypeVar\nfrom pydantic import BaseModel\n\nT = TypeVar(\"T\")\n\nclass Page(BaseModel, Generic[T]):\n    items: list[T]\n    total: int\n    page: int\n    size: int\n\nclass UserOut(BaseModel):\n    id: int\n    name: str\n\n@app.get(\"\u002Fusers\", response_model=Page[UserOut])\nasync def list_users(page: int = 1, size: int = 20):\n    users, total = await db.paginate_users(page, size)\n    return Page(items=users, total=total, page=page, size=size)\n```\n\nPydantic v2 fully supports generic models and FastAPI generates the correct\nOpenAPI schema for each concrete instantiation.\n\nRule of thumb: define one `Page[T]` generic model and reuse it across all list\nendpoints — it keeps pagination schemas consistent and avoids duplication.\n",14,null,{"description":11},"FastAPI type hints interview questions — how Python annotations drive parameter parsing, validation, editor support and OpenAPI schema generation.","fastapi\u002Ffundamentals\u002Ftype-hints","Type Hints & FastAPI","Fundamentals","fundamentals","2026-06-20","Nr51v6fMWEJZeiFZKRQE-kYRliezxU93jinjdgYPU5E",[92,96,99,103,104],{"subtopic":93,"path":94,"order":95},"Async Basics","\u002Ffastapi\u002Ffundamentals\u002Fasync-basics",1,{"subtopic":97,"path":98,"order":12},"Request Lifecycle","\u002Ffastapi\u002Ffundamentals\u002Frequest-lifecycle",{"subtopic":100,"path":101,"order":102},"Path Operations","\u002Ffastapi\u002Ffundamentals\u002Fpath-operations",3,{"subtopic":86,"path":21,"order":20},{"subtopic":105,"path":106,"order":107},"OpenAPI & Docs","\u002Ffastapi\u002Ffundamentals\u002Fopenapi-docs",5,1782244112598]