The idea in one paragraph
-> list[Products] or -> Products describe what you return when the request succeeds (usually HTTP 200).
raise HTTPException(status_code=404, detail="...") stops the handler and sends a 4xx response with {"detail": "..."}. A raise is not a return, so it does not clash with your success type.
response_model documents and serializes the 2xx body; it does not replace error handling.
How Products, list[Products] and HTTPException fit together in one small FastAPI app.
Who this is for and what puzzle we solve
If you’ve written FastAPI routes, you’ve probably seen both of these in the same function:
A return type like -> Products or -> list[Products]
A line like raise HTTPException(status_code=404, detail="...")
It can feel contradictory: “Doesn’t the function promise to return a product? Why is it allowed to raise?”
One self-contained example: Everything below is a single friendly script
from fastapi import FastAPI, HTTPException, Query from pydantic import BaseModel app = FastAPI() class Products(BaseModel): pid: str name: str category: str price: float products: list[Products] = [ Products(pid="p1", name="Item A", category="Electronics", price=99.0), Products(pid="p2", name="Item B", category="Electronics", price=49.0), Products(pid="p3", name="Item C", category="Toys", price=19.0), ] @app.get("/products/category/{category_name}", response_model=list[Products]) def get_products_by_category( category_name: str, limit: int | None = Query(default=None, ge=1), ) -> list[Products]: want = category_name.strip().casefold() filtered = [p for p in products if p.category.casefold() == want] out = filtered if limit is None else filtered[:limit] if not out: raise HTTPException( status_code=404, detail="No products in this category", ) return out @app.get("/products/{pid}", response_model=Products) def get_product_by_pid(pid: str) -> Products: found = next((p for p in products if p.pid == pid), None) if found is None: raise HTTPException(status_code=404, detail="Product not found") return found
What -> Products, list[Products] mean
For Python / your IDE
They’re type hints: they say “on success, this function returns a Products instance” or “a list of Products.” Tools like Pyright/mypy and editors use that for checks and autocomplete.For FastAPI: When you also set response_model=Products (or list[Products]) on the route, the declared response shape matches what you return on success. FastAPI uses that to:
Serialize the return value to JSON (and filter fields if you use response_model_include / etc.).
Build OpenAPI (“200 response body looks like this schema”).
If you only wrote -> Products and did not set response_model, recent FastAPI can still infer the response model from the annotation in many cases — but being explicit with response_model is clear and common.
Where HTTPException fits
raise HTTPException is not a return value. Execution leaves the function through the exception system, so there is no conflict with -> Products: the type means “when the handler finishes normally, it’s a Product.”
Type checkers understand that after raise, the rest of the branch doesn’t run.
