type

type Name = value creates a TypeAliasType. The value is not evaluated until you ask for __value__, so an alias can refer to names defined later — even itself.

DefinitionsPython 3.12+Live demo
alias
type Vector = list[float]
generic alias
type Pair[T] = tuple[T, T]
bound / constraints
type Nums[T: (int, float)] = list[T]
recursive
type Tree = list[Tree | int]
Use for
naming a complex type hint: type JSON = dict[str, JSON] | list[JSON] | ...
Result
binds Name to a typing.TypeAliasType object
Pairs with
type hints, typing, generics (class C[T], def f[T])
Watch out
an alias is not a class — you cannot call it or use it with isinstance()

Demo

Live evaluation
make() logs when it runs. Nothing is logged when the alias is created; the first __value__ access evaluates it, later ones reuse the result.
Try:
Inputs
valueanynumber or text
Code
log = []

def make(value):
    log.append('evaluated')
    return value

type Alias = make(42)
before = list(log)
Alias.__value__
Alias.__value__
(before, log, Alias.__value__)
Result
([], ['evaluated'], 42)

In the lazy tab, before is empty: creating the alias ran nothing. Three __value__ reads produce a single log entry, because the value is computed once and cached. With 1e400 the code shows inf, which is not a Python name — and the NameError appears only at the first __value__ access, not on the type line. In the generic tab, Pair[...] builds a parameterised alias holding whatever you passed; nothing validates it against T at runtime — that is the type checker’s job.

Syntax slots

NameTypeRequiredDescription
NameidentifieryesThe alias name. Bound like an assignment in the current scope.
[T, ...]type paramsnoType parameters, making the alias generic: T, T: bound, T: (A, B), *Ts, **P. Since 3.13 a default: T = int.
expressionexpressionyesThe aliased type. Evaluated lazily, on first access of Name.__value__, then cached.

Common patterns

Recursive JSON type
Lazy evaluation lets the alias mention itself.
type JSON = dict[str, JSON] | list[JSON] | str | int | float | bool | None
Generic alias with a bound
T must be a subtype of the bound for type checkers.
from collections.abc import Hashable

type Index[K: Hashable] = dict[K, list[int]]
Aliases in annotations
Use the alias exactly like the type it names.
type UserId = int

def load(uid: UserId) -> dict[str, str]:
    ...
Pre-3.12 equivalent
typing.TypeAlias (3.10+, deprecated since 3.12) marks an ordinary, eager assignment as an alias.
from typing import TypeAlias

Vector: TypeAlias = list[float]

Examples

1. A simple alias
type Vector = list[float] (Vector, Vector.__value__)
Returns
(Vector, list[float])
2. It is a TypeAliasType object
type UserId = int type(UserId)
Returns
<class 'typing.TypeAliasType'>
3. Forward references just work
type Tree = list[Tree | int] Tree.__value__
Returns
list[Tree | int]
4. Generic alias
type Pair[T] = tuple[T, T] (Pair.__type_params__, Pair[int])
Returns
((T,), Pair[int])
5. Type parameter default (3.13)
type Opt[T = int] = T | None Opt.__type_params__[0].__default__
Returns
<class 'int'>
6. type is still the builtin
(type(3), type('abc').__name__)
Returns
(<class 'int'>, 'str')
7. Soft keyword: still a valid name
type = 5 type
Returns
5

Pitfalls

1. Treating the alias as a class
A type alias is a TypeAliasType, not the type it names. You cannot call it or pass it to isinstance(). Use __value__ (or the real type) at runtime.
isinstance with the alias
type Vector = list[float]
isinstance([1.0], Vector)
TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union
check the runtime type
type Vector = list[float]
isinstance([1.0], list)
True
2. Expecting TypeAlias to be lazy
X: TypeAlias = ... is an ordinary assignment, evaluated immediately, so a forward reference to a class defined later fails. The type statement defers evaluation.
eager TypeAlias
from typing import TypeAlias
Tree: TypeAlias = list[Node]
class Node: pass
NameError: name 'Node' is not defined. Did you mean: 'None'?
type statement
type Tree = list[Node]
class Node: pass
Tree.__value__
list[__main__.Node]
3. Comparing the alias with the aliased type
The alias object is not equal to its value. Compare __value__ if you need that at runtime.
alias == type
type Vector = list[float]
Vector == list[float]
False
compare __value__
type Vector = list[float]
Vector.__value__ == list[float]
True

When to use

Use it
  • Naming a long or repeated type hint (unions, nested generics, callables)
  • Recursive types and forward references — no quotes needed
  • Generic aliases: type Result[T] = T | Error
Reach for something else
  • Code that must run on Python 3.11 or older → Name: TypeAlias = ... (typing)
  • A distinct type for type checkers (UserId must not accept any int) → typing.NewType
  • Anything you need to instantiate or check with isinstance() → use the class itself

Notes

CPython impl
The value is compiled into an annotation scope (like a tiny function); TypeAliasType calls it on the first __value__ access
Soft keyword
type acts as a keyword only where a statement starts with type Name ... = ; everywhere else it is the ordinary builtin name, so type(x) and variables named type keep working
Scope
Class-body names are visible to the alias value (annotation scopes can see the enclosing class namespace), unlike in methods

FAQ

The type statement (3.12+) creates a TypeAliasType whose value is evaluated lazily, supports type parameters directly and allows forward and recursive references without quotes. X: TypeAlias = ... (3.10+) is a normal assignment with a marker annotation: evaluated at once, so forward references must be strings. typing.TypeAlias is deprecated since 3.12 in favour of the type statement.

History

3.12
The type statement was added (PEP 695), together with type parameter lists for classes and functions.
3.13
Type parameters can have default values (PEP 696).