Lesson 25 · Intermediate · 19 min

Python Type Hints

Make the shape of your data visible to readers and tools.

type hintsannotationslist[int]Optional returns

New staff read the order dict and guess what 'qty' means. A type hint says it is an int before anyone runs the code. What does Python actually enforce at runtime?

Type hints are optional annotations that describe the values a function expects and returns. Python does not enforce them at runtime, but editors, linters, and teammates can use them to catch mistakes before the program runs. You will also describe callbacks, JSON-like records, and relationships between input and output types.

Annotate a function

The colon after a parameter names its expected type, and the arrow names the return type. Hints are stored in __annotations__, but they do not convert values. Passing a non-string still runs until the code itself fails.

def greet(name: str) -> str:
    return f"Hello, {name}"
print(greet("Ada"))

Parameters and return type

Describe collections

list[int] says every element is an int. Built-in generics work in modern Python and read clearly. Hints describe the interface; they do not check each item when the function is called.

def total(values: list[int]) -> int:
    return sum(values)
print(total([1, 2, 3]))

A typed list

Show that a value may be missing

dict.get returns None when a key is absent, so the return type should include None. The union syntax float | None states that possibility directly. Callers can then handle the missing case before doing arithmetic.

def find_price(prices: dict[str, float], item: str) -> float | None:
    return prices.get(item)
print(find_price({"tea": 3.5}, "tea"))
print(find_price({"tea": 3.5}, "coffee"))

Maybe a value, maybe None

Callable and Iterable describe behavior

A hint can describe what a function accepts, not just a built-in type. Callable[[int], int] says the argument is a function from int to int; Iterable[int] accepts lists, generators, and other streams. The hint helps editors without changing runtime behavior.

from collections.abc import Callable, Iterable

def apply_all(values: Iterable[int], operation: Callable[[int], int]) -> list[int]:
    return [operation(value) for value in values]

print(apply_all([1, 2, 3], lambda n: n * 2))

Annotate the callback, not just the list

TypedDict describes a record shape

A TypedDict names the keys and value types of a dictionary, which is useful at JSON boundaries. It is still a plain dict at runtime; no key or type check happens automatically. Validate the decoded data separately before trusting it.

from typing import TypedDict

class Order(TypedDict):
    drink: str
    price: float
    quantity: int

order: Order = {"drink": "latte", "price": 4.5, "quantity": 2}
print(order["drink"], order["price"] * order["quantity"])

Name the keys without changing the runtime

TypeVar keeps input and output related

A TypeVar says the same type flows through a function. first(list[int]) returns int | None, while first(list[str]) returns str | None. Without the variable, the best you could say is object, which loses useful information for callers.

from typing import TypeVar
T = TypeVar("T")

def first(items: list[T]) -> T | None:
    return items[0] if items else None

print(first([1, 2, 3]))
print(first(["latte"]))
print(first([]))

Preserve the element type through the call

Key points

  • Parameter hints follow a colon; return hints follow ->.
  • list[int] and dict[str, float] describe collection contents.
  • float | None means the value may be missing.
  • Hints are documentation and tooling input, not runtime enforcement.
  • Hints describe interfaces; TypedDict and TypeVar add shape and relationships, not runtime checks.

Common mistakes

  • Assuming Python raises TypeError just because a hint is violated.
  • Putting a list[int] hint in a comment instead of the function signature.
  • Forgetting None in a return type when a lookup can fail.
  • Believing a violated annotation raises TypeError by itself.
Your turn

Annotate double(n: int) -> int, print double(4), and annotate a function that returns str | None.

Try it yourself

Change the values, predict the output, then run the code. The Python runtime is downloaded the first time you press Run, so the first run can take a few seconds.

In-browser PythonRuns locally · no account
First run downloads Pyodide from a CDN. Use Run code after editing.

Practice this lesson

12 questions with immediate explanations.

See all 12 answersStart practice
← assertgenerators →