Skip to content

Path & Query Parameters Interview Questions & Answers

14 questions Updated 2026-06-20 Share:

FastAPI path and query parameter interview questions — type coercion, optional params, validation constraints, multi-value query params and Path/Query helpers.

14 of 14

Declare the parameter name inside curly braces in the path string and add a matching argument to the handler function. FastAPI parses and type-coerces it automatically.

@app.get("/items/{item_id}")
async def get_item(item_id: int):
    return {"item_id": item_id}
# GET /items/42 → {"item_id": 42} (integer, not string)

If the value can't be coerced to the declared type, FastAPI returns 422.

Rule of thumb: always type-annotate path parameters so FastAPI validates the URL segment before the handler runs.

Declare a function parameter with a type annotation and no default value. FastAPI treats any simple type not in the path template as a query parameter.

@app.get("/items")
async def search_items(q: str):
    return {"q": q}
# GET /items?q=laptop → {"q": "laptop"}
# GET /items        → 422 (required param missing)

Rule of thumb: no default → required query param; add = None or = value to make it optional.

Assign a default in the function signature. FastAPI uses it when the client omits the parameter.

@app.get("/items")
async def list_items(
    page: int = 1,
    size: int = 20,
    active: bool = True,
):
    return {"page": page, "size": size, "active": active}
# GET /items → {"page": 1, "size": 20, "active": true}
# GET /items?page=3 → {"page": 3, "size": 20, "active": true}

Rule of thumb: choose defaults that represent the most common use case so clients don't need to pass boilerplate on every request.

FastAPI accepts a flexible range of truthy/falsy string values and converts them to Python bool:

  • Truthy: "1", "true", "on", "yes" (case-insensitive)
  • Falsy: "0", "false", "off", "no" (case-insensitive)
@app.get("/items")
async def list_items(active: bool = True):
    ...
# GET /items?active=false → active = False
# GET /items?active=0    → active = False
# GET /items?active=yes  → active = True

Any other value triggers a 422 validation error.

Rule of thumb: use bool for feature flags/filters in query params — FastAPI's flexible string-to-bool coercion covers all common client conventions.

FastAPI gives priority to the path parameter. A parameter name that appears in the route template /{name} is always a path parameter; you cannot also read it from the query string with the same name in the same handler.

@app.get("/items/{id}")
async def get_item(id: int, q: str | None = None):
    # id → from path, q → from query
    ...

If you genuinely need both a path and query parameter with the same name, use an alias: Query(alias="id") — though this is a design smell.

Rule of thumb: keep path and query parameter names distinct; collision means you should rethink the route design.

Path() is FastAPI's parameter helper for adding metadata and validation constraints to path parameters while keeping the type annotation clean.

from typing import Annotated
from fastapi import Path

@app.get("/items/{item_id}")
async def get_item(
    item_id: Annotated[int, Path(title="Item ID", ge=1, le=999_999)],
):
    return {"id": item_id}

Constraints (ge, le, gt, lt) are enforced at validation time and reflected in the OpenAPI schema. You can also pass description, example, and deprecated.

Rule of thumb: use Path() whenever a path parameter has a meaningful range, description, or example worth documenting in the schema.

Query() adds string constraints (min_length, max_length, pattern), numeric bounds, metadata (title, description, example), and the ability to collect multi-value parameters.

from typing import Annotated
from fastapi import Query

@app.get("/search")
async def search(
    q: Annotated[str, Query(
        min_length=3,
        max_length=100,
        description="Search term",
        example="fastapi",
    )],
    sort: Annotated[str, Query(pattern=r"^(asc|desc)$")] = "asc",
):
    ...

Rule of thumb: add Query() the moment you need any constraint or documentation on a query parameter — it's zero runtime cost and improves the schema immediately.

Annotate with list[T] and wrap with Query():

from typing import Annotated
from fastapi import Query

@app.get("/items")
async def filter_items(
    tags: Annotated[list[str], Query()] = [],
):
    return {"tags": tags}
# GET /items?tags=python&tags=web → {"tags": ["python", "web"]}

Without Query(), list[str] would be interpreted as a JSON body parameter. An empty list default (= []) means the param is optional; use = Query(min_length=1) on the list if at least one tag is required.

Rule of thumb: list[T] + Query() = multi-value param; the client repeats the key multiple times in the query string.

Pass alias= to Query() or Path(). FastAPI reads the value from the alias key in the request but binds it to the Python name in the handler.

from typing import Annotated
from fastapi import Query

@app.get("/items")
async def list_items(
    item_query: Annotated[str | None, Query(alias="item-query")] = None,
):
    return {"query": item_query}
# GET /items?item-query=foo → item_query = "foo"

This is useful when the URL convention requires hyphens (which are invalid Python identifiers) or when you're preserving backward-compatible parameter names.

Rule of thumb: use alias to bridge the gap between URL naming conventions (hyphens) and Python naming conventions (underscores).

Pass deprecated=True to Query() or Path(). The parameter still works at runtime — it's marked deprecated in the generated schema only.

from typing import Annotated
from fastapi import Query

@app.get("/items")
async def list_items(
    q: Annotated[str | None, Query()] = None,
    search: Annotated[str | None, Query(deprecated=True)] = None,  # old alias
):
    effective_q = q or search
    ...

Swagger UI renders deprecated parameters with a strikethrough and a warning badge.

Rule of thumb: mark old parameter names deprecated rather than removing them immediately — gives clients a migration window while keeping the schema honest.

Constraint Meaning
ge=n greater than or equal (>=)
gt=n strictly greater than (>)
le=n less than or equal (<=)
lt=n strictly less than (<)
multiple_of=n value must be a multiple of n
from typing import Annotated
from fastapi import Query, Path

@app.get("/items/{item_id}")
async def get_item(
    item_id: Annotated[int, Path(ge=1)],           # ID must be positive
    page: Annotated[int, Query(ge=1, le=100)] = 1, # page 1-100
    price_min: Annotated[float, Query(gt=0)] = 0.0,
):
    ...

Constraints are reflected in the OpenAPI JSON Schema properties so client validators can enforce them before the request is sent.

Rule of thumb: apply ge=1 to all ID path parameters — negative or zero IDs are almost always bugs.

Constraint Meaning
min_length=n minimum string length
max_length=n maximum string length
pattern=r"..." regex the value must match
from typing import Annotated
from fastapi import Query

@app.get("/users")
async def search_users(
    username: Annotated[str, Query(min_length=3, max_length=50, pattern=r"^\w+$")],
):
    ...
# GET /users?username=al → 422 (min_length=3)
# GET /users?username=al!ce → 422 (pattern)

Rule of thumb: always set max_length on free-text query params to prevent accidental DoS from clients sending huge query strings.

Pass include_in_schema=False to Query(). The parameter still works at runtime but won't appear in /openapi.json or the docs.

from typing import Annotated
from fastapi import Query

@app.get("/items")
async def list_items(
    q: str | None = None,
    _internal_trace_id: Annotated[str | None, Query(include_in_schema=False)] = None,
):
    ...

Rule of thumb: use include_in_schema=False for internal/infra params (tracing IDs, A/B test flags) that aren't part of the public API contract.

Path parameters are for identifying a specific resource:

GET /users/{user_id}       # identifies a specific user
GET /orders/{order_id}/items  # items within a specific order

Query parameters are for filtering, sorting, searching or pagination of a collection:

GET /users?role=admin&page=2   # filter users by role, paginate
GET /orders?status=pending     # filter orders

Mixing them up leads to ugly URLs like /users?id=42 (should be /users/42) or /users/admin for a filter (should be /users?role=admin).

Rule of thumb: if removing the identifier would leave a meaningless URL (/users/ is just a list), it belongs in the path; if it's a filter, it's a query param.

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel