Advanced Type Hints

Advanced type hints let you describe flexible, reusable, and precisely-shaped types — generics, structural protocols, and typed dictionaries — that static checkers verify before your code ever runs.

Learn Advanced Type Hints in our free Python course — an interactive lesson with runnable examples, a practice exercise and a quick reference.

Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

These are the tools that make large Python codebases maintainable. The interpreter ignores them at runtime, but mypy and your editor turn them into a powerful safety net.

A value that could be one of several types is a Union . A value that could be a given type or None is Optional . Since Python 3.10 you can write both with a clean pipe:

Crucially, these annotations do not run. Python never checks them while executing — a function annotated -> int will return a string without complaint. They exist for tools like mypy and your editor:

A generic lets you write a container once and reuse it for any element type, while keeping the type information. The classic approach uses a TypeVar and Generic :

Python 3.12 added a much cleaner built-in syntax — no TypeVar import needed. It means the same thing:

Generics are why list[int] and dict[str, float] work: the built-in containers are generic too. Your own classes can join them with either syntax above.

A Protocol describes a shape : any object with the right methods fits, no inheritance required. This is duck typing made checkable. Add @runtime_checkable to also allow isinstance tests:

A TypedDict nails down the keys and value types of a dictionary, and Callable describes a function's signature when you pass functions around:

Complete the runtime-checkable Protocol so the isinstance checks work. Replace each ___ and match the expected output.

Decorator blank: runtime_checkable — so the class is decorated with @runtime_checkable , which lets isinstance work against the Protocol.

✅ Run a static checker — mypy file.py — to actually catch type mismatches.

❌ isinstance against a Protocol without @runtime_checkable

✅ Decorate the Protocol with @runtime_checkable before using it in isinstance .

❌ Thinking a TypedDict is a special class at runtime

✅ A TypedDict is just a dict at runtime; only a type checker flags the misspelled key. Verify with mypy.

Combine a Protocol, a TypedDict, and a Callable: render any object that has a label, store records as typed dicts, and apply a transform function.

Go deeper with the official Python documentation:

Lesson complete — your types are precise and reusable now!

You can express alternatives with Optional , Union , and X | Y , write generic containers with TypeVar (or 3.12's inline syntax), describe shapes with Protocol and TypedDict , type functions with Callable , and you understand that hints are checked by mypy, not the interpreter.

🚀 Up next: Checkpoint: The Standard Library — put it all together in a build challenge.

Practice quiz

Optional[int] is shorthand for which of these?

Optional[int] means 'int or None' — exactly Union[int, None]. In Python 3.10+ you can also write int | None.

Which is the modern (Python 3.10+) way to write 'int or None'?

  • int | None
  • int & None
  • int + None
  • Optional(int, None)

Answer: int | None. The pipe syntax int | None means the same as Optional[int] / Union[int, None] and is the cleaner modern form.

At runtime, what happens when an argument's value does not match its type hint?

  • Python raises a TypeError immediately
  • The value is auto-converted to the hinted type
  • The function returns None
  • Nothing — Python runs normally; only static checkers flag it

Answer: Nothing — Python runs normally; only static checkers flag it. Hints are not enforced by the interpreter. A function annotated -> int can return a string and run fine; mypy would catch the mismatch beforehand.

What does a TypeVar (e.g. T = TypeVar('T')) provide?

  • A runtime type-conversion helper
  • A placeholder for 'some type' used to build generics
  • A constant that must always be int
  • A way to disable type checking

Answer: A placeholder for 'some type' used to build generics. A TypeVar is a stand-in for an arbitrary type. Combined with Generic[T], it lets a class like Stack[T] preserve element type information.

How do you make a class generic using the classic typing syntax?

Subclassing Generic[T] with a TypeVar T is the classic approach. The inline form was added in Python 3.12.

What does a Protocol describe?

  • A required base class every conforming object must inherit
  • A network message format
  • A shape — the methods/attributes an object must have, no inheritance required
  • A frozen set of constants

Answer: A shape — the methods/attributes an object must have, no inheritance required. A Protocol enables structural typing: any object with the right methods fits, without inheriting from the Protocol.

What must you add to a Protocol to use it with isinstance() at runtime?

  • @dataclass
  • @runtime_checkable
  • @staticmethod
  • Nothing — Protocols always support isinstance

Answer: @runtime_checkable. Decorating the Protocol with @runtime_checkable lets isinstance(obj, MyProtocol) work; otherwise it raises a TypeError.

What does a TypedDict let you specify?

  • A dict whose keys are checked for spelling at runtime
  • An immutable dictionary
  • A dict that can only contain integers
  • The exact keys of a dict and the type of each value

Answer: The exact keys of a dict and the type of each value. A TypedDict declares each key and its value type (e.g. class Movie(TypedDict): title: str; year: int). At runtime it is just a normal dict.

What does Callable[[int], int] describe?

  • A list containing an int
  • A function taking one int and returning an int
  • A callable that returns nothing
  • An int that is also callable

Answer: A function taking one int and returning an int. Callable[[arg types], return type] describes a function's signature — here one int parameter and an int return value.

When using @runtime_checkable, why does isinstance('hello', Sized) return True if Sized requires __len__?

  • All objects pass runtime-checkable Protocols
  • Strings explicitly inherit from Sized
  • Structural typing — str has a __len__ method, so it fits the Protocol
  • isinstance ignores the Protocol's methods

Answer: Structural typing — str has a __len__ method, so it fits the Protocol. Protocols match on structure: because str defines __len__, it satisfies a Sized Protocol — no inheritance from Sized needed.