Python for JS Devs · Fundamentals

Type hints

You know TypeScript. Python's type hints are the same idea with different syntax — and they're the engine FastAPI runs on.

Lesson 06Time ~13 min Win Annotate a function fullyPrereq Lesson 05

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.

1 · The syntax, mapped from TS

TypeScript
name: string
count: number
ok: boolean
tags: string[]
scores: Record<string, number>

function greet(n: string): string {}
Python
name: str
count: int
ok: bool
tags: list[str]
scores: dict[str, int]

def greet(n: str) -> str: ...   # -> is the return type

The return type goes after ->. Container types use square brackets: list[str], dict[str, int], tuple[int, int].

2 · Optional, unions & None

TypeScript
email: string | null
id: string | number
role?: string
Python (3.10+)
email: str | None        # the "optional" idiom
id: str | int            # union, same | syntax as TS
def f(role: str | None = None): ...  # optional param
Modern vs legacy You may see Optional[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.

3 · The crucial catch: hints don't run

Read this twice Python does not enforce type hints at runtime by itself. 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.

4 · Variables and richer shapes

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

🎯 Your win

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.

Check yourself

Q1. How do you type "string or None" in modern Python?
str | None (3.10+) is the modern optional; older code uses Optional[str].
Q2. By default, plain Python type hints at runtime are:
Like TS, hints are erased for runtime purposes — unless a library like Pydantic deliberately reads them.
Q3. A function's return type is written:
def f(x: int) -> str: — the return type follows ->.
Primary source FastAPI — Python Types Intro. Written specifically to teach the hints FastAPI needs. See the type hints cheat sheet.
💬 Ask your teacher: how do I actually run a type checker with uv? (Answer involves uvx ty or uvx mypy — ask and I'll walk you through it.)