What does a type alias like `Vector = list[float]` change at runtime and for a type checker?
answer
- Only a nicer name for a shape
- Nothing new exists at runtime
- The checker expands it everywhere
- Interchangeable with what it stands for
- Mark it with TypeAlias or type
basics
~20 sNothing at runtime: the name is simply bound to the same list[float] object. For a type checker the alias is transparent, so Vector and list[float] mean exactly the same thing everywhere. You buy readability, not a new type.
solid answer
~50 s`Vector = list[float]` is an ordinary assignment. The right-hand side is evaluated once and the name is bound to that generic-alias object, so nothing new exists at runtime and no values are validated. A type checker treats the alias as **transparent**: every use of `Vector` expands to `list[float]`, so passing a plain `list[float]` where a `Vector` is expected is fine, and vice versa. That is the whole point of an alias -- it shortens a long nested container or union and gives it a domain name without changing typing behaviour. Because a bare assignment is ambiguous with a variable that happens to hold a type object, you can mark it explicitly with `typing.TypeAlias` (Python 3.10) or, from 3.12, declare it with the `type` statement. If you want a name the checker refuses to confuse with its underlying type, an alias is the wrong tool -- that is what `typing.NewType` is for.
code
python · 13 linesfrom typing import TypeAlias
Vector: TypeAlias = list[float]
Matrix: TypeAlias = list[Vector]
def scale(v: Vector, k: float) -> Vector:
return [x * k for x in v]
print(Vector == list[float])
print(Matrix)
print(scale([1.0, 2.0], 3.0))go deeper
Be ready to say that an alias is only a better name: the interpreter binds the name to the same object and enforces nothing. Know one way to declare it explicitly, either typing.TypeAlias or the type statement.
Explain transparency concretely -- the checker expands the alias, so the alias and its target are mutually assignable -- and say why a bare assignment is ambiguous with a variable holding a class object.
Show judgement about when to name a shape at all: repeated nested containers and long unions earn an alias, one-off signatures do not. Be able to route a request for a distinct type to NewType and a request for metadata to Annotated instead.
Own the codebase convention: which spelling the repo uses given its minimum Python version, whether shared aliases live in one dependency-free module, and how much of the domain vocabulary should be transparent aliases versus enforced shapes.
### The assignment is just an assignment `Vector = list[float]` runs at import time like any other statement. `list[float]` builds a `types.GenericAlias` object, and the name `Vector` is bound to it. There is no new class, no registration with the interpreter, and no runtime checking of what a function actually receives. You can prove it in a REPL: `Vector == list[float]` is `True`, and `Vector([1, 2])` builds a perfectly ordinary `list` -- the annotation says `float`, but nothing enforces it. That is worth stating plainly because it is the most common junior misconception: annotations and aliases are documentation that a *separate* tool reads. The interpreter stores them and moves on. ### What "transparent" means to a checker A checker expands the alias wherever it appears. Given `Vector = list[float]`, these two signatures are indistinguishable to it: ```python def scale(v: Vector, k: float) -> Vector: ... def scale(v: list[float], k: float) -> list[float]: ... ``` So a caller may pass any `list[float]` and a function may return any `list[float]`. There is no "a Vector is not a list" error to be had, because the alias is not a type -- it is a spelling. This is exactly why aliases are cheap and safe to introduce: adding one can never make previously valid code fail to check. The payoff is readability at the point of use. A signature reading `dict[str, list[tuple[str, int | None]]]` in four places is a maintenance hazard; naming it once and using the name is the same type with a much better label, and changing the underlying shape is then a one-line edit. ### Declaring an alias unambiguously A bare assignment is ambiguous. Does `X = int` mean "X is an alias for int" or "X is a module-level variable whose value happens to be the `int` class"? Checkers use heuristics, and the heuristics occasionally guess wrong -- especially for aliases whose right-hand side is a plain class name, or which are assigned inside a function or conditional branch. Two spellings remove the doubt: * `Vector: TypeAlias = list[float]` -- the `typing.TypeAlias` marker, added in **Python 3.10** (PEP 613). It is a promise to the checker, and the annotation itself does nothing at runtime. * `type Vector = list[float]` -- the `type` statement, added in **Python 3.12** (PEP 695). This is now the preferred spelling for new code; it also makes the right-hand side lazily evaluated, which matters for forward references. Its runtime object and mechanics are a topic of their own. Code that must run on 3.10 or 3.11 uses the `TypeAlias` marker; code pinned to 3.12+ uses the statement. Both still describe a transparent alias. ### Aliases can be nested and parameterised Aliases compose: `Matrix = list[Vector]` is `list[list[float]]`. They can also carry type parameters. In the pre-3.12 spelling that means writing the alias in terms of a `typing.TypeVar` and subscripting the alias at the point of use: ```python from typing import TypeAlias, TypeVar T = TypeVar("T") Pair: TypeAlias = tuple[T, T] def first(p: Pair[int]) -> int: return p[0] ``` `Pair[int]` evaluates to `tuple[int, int]`, again at runtime, again with no checking. ### Where an alias is the wrong tool Three neighbours are routinely confused with it, and being able to say which you want is the real interview signal: * You want a name the checker keeps **distinct** from its base type, so an ordinary `int` cannot be passed where a user id is expected: that is `typing.NewType`, not an alias. * You want to attach **metadata** to a type for some runtime tool to read: that is `typing.Annotated`, which is still the same type to the checker but carries extra objects. * You want to describe a **dictionary with known keys**: a `TypedDict` describes the keys; `dict[str, str]` (aliased or not) describes only "string keys, string values". A final practical note: because the alias is evaluated eagerly, its right-hand side must already be importable at that point in the module. Names that are not yet defined -- including the alias itself, for a recursive shape -- need a string forward reference or the lazily-evaluated 3.12 statement form.
- How does a type checker decide that `X = int` is an alias rather than a variable holding a class object?It guesses, using heuristics about the right-hand side and where the assignment appears -- module level, no annotation, a type-like expression. The guess can be wrong, particularly for a bare class name or an assignment inside a branch. Removing the guess is exactly why `typing.TypeAlias` (3.10) and the `type` statement (3.12) exist: both state the intent outright, and a checker will then complain if the right-hand side is not a valid type expression.
- Can a type alias take type parameters, and how did that work before 3.12?Yes. Before 3.12 you write the alias in terms of a `typing.TypeVar` and subscript it at the point of use: `T = TypeVar('T')`, `Pair: TypeAlias = tuple[T, T]`, then annotate with `Pair[int]`, which evaluates to `tuple[int, int]`. From 3.12 the `type` statement declares the parameters inline. Either way the alias stays transparent -- `Pair[int]` and `tuple[int, int]` are the same type to the checker.
- Does an alias give you any runtime protection against passing the wrong element type?None. `Vector = list[float]` does not stop anyone calling `scale(['a', 'b'], 2)`; that fails later inside the function, if at all. Annotations and aliases are consumed by static tools and by libraries that deliberately introspect them. If you need values checked at a boundary, you need an explicit validation step -- the type system will not add one for you.
saying these in an interview costs you the question
- Says the alias creates a new type at runtime
- Expects a checker to reject a plain list where the alias is expected
- Confuses a transparent alias with NewType
- Believes an alias validates element types when the function is called
- Thinks `TypeAlias` changes behaviour rather than declaring intent