Python for JS Devs · Fundamentals
You know TypeScript. Python's type hints are the same idea with different syntax — and they're the engine FastAPI runs on.
This is the most important fundamentals lesson for your mission. FastAPI and Pydantic don't just accept type hints — they read them at runtime to validate requests, serialize responses, and generate docs. Coming from TypeScript, you already own the mental model.
name: string
count: number
ok: boolean
tags: string[]
scores: Record<string, number>
function greet(n: string): string {}name: str
count: int
ok: bool
tags: list[str]
scores: dict[str, int]
def greet(n: str) -> str: ... # -> is the return typeThe return type goes after ->. Container types use square brackets: list[str], dict[str, int], tuple[int, int].
email: string | null
id: string | number
role?: stringemail: str | None # the "optional" idiom
id: str | int # union, same | syntax as TS
def f(role: str | None = None): ... # optional paramOptional[str] and List[str] (capitalized, from typing) in older code. On Python 3.10+ prefer the lowercase built-ins and | None. The AI-generated project you had used a mix — now you know why.greet(123) will happily run. Hints are checked by tools — your editor + a type checker (mypy/ty/pyright) — exactly like TS types vanish at runtime. The exception is Pydantic/FastAPI, which deliberately read the hints and do validate at runtime. That's the whole trick behind FastAPI.
So type hints have two payoffs: (1) editor + checker safety during dev, and (2) they power FastAPI request/response validation. Both reasons to write them everywhere.
retries: int = 3
user: dict[str, str | int] = {"name": "kd", "age": 30}
from typing import Literal
status: Literal["active", "banned"] # like a TS string-literal union
Write a fully annotated function: def summarize(scores: list[int], label: str | None = None) -> dict[str, int]: that returns {"count": ..., "total": ...}. Run it in the REPL, then call it with a wrong type and confirm Python doesn't complain at runtime — proving hints are advisory until a checker (or Pydantic) reads them.
str | None (3.10+) is the modern optional; older code uses Optional[str].def f(x: int) -> str: — the return type follows ->.uvx ty or uvx mypy — ask and I'll walk you through it.)