Correct. mypy can parse the new syntax, as long as you use Python 3.12 to execute mypy, but it has not yet been updated to understand the semantics behind the new syntax. See https://gh-proxy.deno.dev/python/mypy/issues/15238 for more information on the effort to upgrade mypy.

In the meantime, if you want to use mypy to type-check your code, you cannot use the new syntax. Conversely, if you want to switch to the new syntax, you'll have to use another tool (PyRight, for example) which does support the new syntax.

Answer from chepner on Stack Overflow
🌐
Python documentation
docs.python.org › 3 › library › typing.html
typing — Support for type hints — Python 3.14.3 documentation
1 month ago - from collections.abc import Iterable from typing import TypeVar S = TypeVar("S") Response = Iterable[S] | int · Changed in version 3.7: Generic no longer has a custom metaclass. Changed in version 3.12: Syntactic support for generics and type aliases is new in version 3.12.
Discussions

Non-uniqueness of TypeVar on Python versions <3.12 causes resolution issues
In Python 3.12, the new Generic / TypeVar syntax allows us to do this: class Foo[T, U]: type: T info: U class Bar[T](Foo[str, T]): test: T Which, for older versions is functionally equivalent to this: fro… More on discuss.python.org
🌐 discuss.python.org
0
October 28, 2023
python - What is the difference between TypeVar and NewType? - Stack Overflow
You can restrict the possible values ... (e.g. TypeVar(name, bound=int)). Additionally, you can decide whether a type variable is covariant, contravariant, or neither when you declare it. This essentially decides when subclasses or superclasses can be used in place of a generic type. PEP 484 describes these concepts in more detail, and refers to additional resources. Starting in Python 3.12, the following ... More on stackoverflow.com
🌐 stackoverflow.com
Let's talk about python 3.12's new Type Parameter Syntax.
There was probably much discussion on this placement, and there is not point discussing it now because it won't change. If you want to learn more about this, I recommend reading PEP 695 and additionally the following: https://jellezijlstra.github.io/pep695 https://github.com/python/peps/pull/3122 More on reddit.com
🌐 r/Python
43
0
January 26, 2024
python 3.12 typing override function - Stack Overflow
this type of defining function in python looks a little bit weird to me def override[F: type](method: F, /) -> F:... what is the meaning of [F: type] exactly? is it something like, new way of More on stackoverflow.com
🌐 stackoverflow.com
🌐
Reddit
reddit.com › r/learnpython › should i use a typevar?
r/learnpython on Reddit: Should I use a TypeVar?
January 28, 2024 -

In my code I have an alias for a type and mypy is happy about it.

NumberedPaths = list[tuple[str, str]]

Should I use TypeVar instead? Is there any better way to define a new type?

🌐
Python.org
discuss.python.org › typing
Non-uniqueness of TypeVar on Python versions <3.12 causes resolution issues
October 28, 2023 - In Python 3.12, the new Generic / TypeVar syntax allows us to do this: class Foo[T, U]: type: T info: U class Bar[T](Foo[str, T]): test: T Which, for older versions is functionally equivalent to this: from typing import Generic, TypeVar FooT ...
🌐
Python
typing.python.org › en › latest › spec › generics.html
Generics — typing documentation
When using the older syntax, the scoping rules are more subtle and complex: A type variable used in a generic function could be inferred to represent different types in the same code block. Example: from typing import TypeVar, Generic T = TypeVar('T') def fun_1(x: T) -> T: ...
🌐
Mypy
mypy.readthedocs.io › en › stable › generics.html
Generics - mypy 1.19.1 documentation
There is also a legacy syntax that relies on TypeVar. Here the number of type arguments must match the number of free type variables in the generic type alias definition. A type variables is free if it’s not a type parameter of a surrounding class or function. Example (following PEP 484: Type aliases, Python 3...
🌐
Python documentation
docs.python.org › 3 › whatsnew › 3.12.html
What's New In Python 3.12 — Python 3.14.3 documentation
February 23, 2026 - type IntFunc[**P] = Callable[P, int] # ParamSpec type LabeledTuple[*Ts] = tuple[str, *Ts] # TypeVarTuple type HashableSequence[T: Hashable] = Sequence[T] # TypeVar with bound type IntOrStrSequence[T: (int, str)] = Sequence[T] # TypeVar with constraints · The value of type aliases and the bound and constraints of type variables created through this syntax are evaluated only on demand (see lazy evaluation). This means type aliases are able to refer to other types defined later in the file. Type parameters declared through a type parameter list are visible within the scope of the declaration and any nested scopes, but not in the outer scope. For example, they can be used in the type annotations for the methods of a generic class or in the class body.
Find elsewhere
🌐
Medium
medium.com › @r_bilan › generics-in-python-3-12-whats-new-in-the-syntax-and-how-to-use-it-now-1024fcdf02f8
Generics in Python 3.12: What’s New in the Syntax and How to Use It Now | by Rostyslav Bilan | Medium
October 2, 2024 - from typing import TypeAlias, TypeVar _T = TypeVar("_T") ListOrSet: TypeAlias = list[_T] | set[_T] But now, with the new syntax in Python 3.12, it can be written as:
🌐
Real Python
realpython.com › python312-typing
Python 3.12 Preview: Static Typing Improvements – Real Python
October 21, 2023 - You use square brackets to parametrize generics in Python. You can write the two examples above as list[int] and tuple[float, str, float], respectively. In addition to using built-in generic types, you can define your own generic classes.
🌐
OneUptime
oneuptime.com › home › blog › how to build generic types with typevar in python
How to Build Generic Types with TypeVar in Python
January 30, 2026 - # Python 3.12+ - cleaner syntax class NewStack[T]: """No need to inherit from Generic or define TypeVar separately.""" def __init__(self) -> None: self._items: list[T] = [] def push(self, item: T) -> None: self._items.append(item) def pop(self) -> T: return self._items.pop() def new_first[T](items: list[T]) -> T: """Generic function with inline type parameter.""" return items[0] # Bounded types in the new syntax class NumberContainer[T: (int, float)]: """T is constrained to int or float.""" def __init__(self, value: T) -> None: self.value = value def doubled(self) -> T: return self.value * 2 # type: ignore
🌐
ArjanCodes
arjancodes.com › blog › python-generics-syntax
Python 3.12 Generics: Cleaner, Easier Type-Hinting | ArjanCodes
June 6, 2024 - Type-hinting generics in Python got a whole lot easier with the release of Python 3.12. No longer do we need to define TypeVars and ParamSpecs. Let me show you how the new generic syntax can make your code cleaner and easier to read! A generic is a type of object whose behavior doesn’t depend on the type it is handling and instead can be used in the same way for many types. An example of a generic is the built-in list[T] object, where T is the generic type.
Top answer
1 of 2
222

The two concepts aren't related any more than any other type-related concepts.

In short, a TypeVar is a variable you can use in type signatures so you can refer to the same unspecified type more than once, while a NewType is used to tell the type checker that some values should be treated as their own type.

Type Variables

To simplify, type variables let you refer to the same type more than once without specifying exactly which type it is.

In a definition, a single type variable always takes the same value.

# (This code will type check, but it won't run.)
from typing import TypeVar, Generic

# Two type variables, named T and R
T = TypeVar('T')
R = TypeVar('R')

# Put in a list of Ts and get out one T
def get_one(x: list[T]) -> T: ...

# Put in a T and an R, get back an R and a T
def swap(x: T, y: R) -> tuple[R, T]:
    return y, x

# A simple generic class that holds a value of type T
class ValueHolder(Generic[T]):
    def __init__(self, value: T):
        self.value = value
    def get(self) -> T:
        return self.value

x: ValueHolder[int] = ValueHolder(123)
y: ValueHolder[str] = ValueHolder('abc')

Without type variables, there wouldn't be a good way to declare the type of get_one or ValueHolder.get.

There are a few other options on TypeVar. You can restrict the possible values by passing in more types (e.g. TypeVar(name, int, str)), or you can give an upper bound so every value of the type variable must be a subtype of that type (e.g. TypeVar(name, bound=int)).

Additionally, you can decide whether a type variable is covariant, contravariant, or neither when you declare it. This essentially decides when subclasses or superclasses can be used in place of a generic type. PEP 484 describes these concepts in more detail, and refers to additional resources.

Addendum: Python 3.12 generic parameter lists

Starting in Python 3.12, the following syntax has been available to declare type variables.

def get_oneT -> T: ...

def swapT, R -> tuple[R, T]: ...

class ValueHolder[T]:
    def __init__(self, value: T): ...
    def get(self) -> T: ...

These declarations are equivalent to those above, but now the type variables are only defined in type signatures within their functions/classes, rather than being stored in regular Python variables. The Python 3.12 release notes contain a summary, as well as links to more-detailed documentation.

NewType

A NewType is for when you want to declare a distinct type without actually doing the work of creating a new type or worry about the overhead of creating new class instances.

In the type checker, NewType('Name', int) creates a subclass of int named "Name."

At runtime, NewType('Name', int) is not a class at all; it is actually the identity function, so x is NewType('Name', int)(x) is always true.

from typing import NewType

UserId = NewType('UserId', int)

def get_user(x: UserId): ...

get_user(UserId(123456)) # this is fine
get_user(123456) # that's an int, not a UserId

UserId(123456) + 123456 # fine, because UserId is a subclass of int

To the type checker, UserId looks something like this:

class UserId(int): pass

But at runtime, UserId is basically just this:

def UserId(x): return x

There's almost nothing more than that to a NewType at runtime. In Python 3.8.1, its implementation was almost exactly as follows:

def NewType(name, type_):
    def identity(x):
        return x
    identity.__name__ = name
    return identity
2 of 2
0

NewType() accepts an unique type parameter. To specialize the function for different types for static typing, you only need a TypeVar here.

Example: Read https://dev.to/decorator_factory/typevars-explained-hmo

🌐
InfoWorld
infoworld.com › home › software development › programming languages › python
The best new features and fixes in Python 3.12 | InfoWorld
October 3, 2023 - The type parameter syntax provides a cleaner way to specify types in a generic class, function, or type alias. Here’s an example taken from the PEP: # the old method from typing import TypeVar _T = TypeVar("_T") def func(a: _T, b: _T) -> _T: ...
🌐
Python.org
discuss.python.org › typing
TypeVars and the new 3.12 syntax: what happend to co-/contravariances? - Typing - Discussions on Python.org
May 30, 2024 - I’m writing this post because I’m curious) With the new Python 3.12 we have got a new syntax for TypeVars, so instead of: T = TypeVar("T") def a_fn(arg: T) -> list[T]: ... we can write: def a_fn[T](arg: T) -> list[T]: ... However with the ...
🌐
Medium
medium.com › @ggb9898 › python-12-4-features-you-need-to-know-2c441dcf5289
Python 12: 4 Features You Need to Know | by Ggb | Medium
September 21, 2023 - The type parameter syntax offers ... example from the PEP: # The old method from typing import TypeVar _T = TypeVar("_T") def func(a: _T, b: _T) -> _T: …...
Top answer
1 of 3
9

The purpose of the TypeVar in this context is to say that the function returns a specific type that is related to the argument's type.

For example, if you did:

a = first([1, 2, 3]) + "foo"

you would get an error, because in this expression T becomes bound to the type int, and so you'd get an error about adding an int and a str.

If you annotated first with Any types as you describe, this would not produce a mypy error (and hence you'd get a TypeError at runtime instead), because the return value of first would always simply be Any.

See the mypy documentation on generics for a lot more examples of how to use typevars: https://mypy.readthedocs.io/en/stable/generics.html

2 of 3
1

The best example is with pydantic.

Imagine I have a function that implements pydantic, and I want that function to be able to handle my pydantic type for records retrieved as a dict from firestore. That code may look something like this:

class MyModel(BaseModel):
    ...

class MyClass:
    def get_records(...) -> Generator[MyModel, None, None]:
        for record in self.client.collection("MyModel").where(...).stream(...):
            body = record.to_dict()
            if body:
                yield MyModel.model_validate(body)

Now that's great and all, but then what if I have multiple models, then I have to define a function for each one, right? Pretty annoying.

Ok what if I use a Union.

class MyModel(BaseModel):
    ...

class MyModel2(BaseModel):
    ...

T: TypeAlias = Union[MyModel, MyModel2]

class MyClass:
    def get_records(
        my_union_type: Type[T],
        collection_name: str
    ) -> Generator[T, None, None]:
        for record in self.client.collection(collection_name).where(...).stream(...):
            body = record.to_dict()
            if body:
                if isinstance(my_union_type, MyModel):
                    yield MyModel.model_validate(body)
                elif isinstance(my_union_type, MyModel2):
                    yield MyModel2.model_validate(body)

As you can see we can now use our new type, but its a bit messy, no? We are calling the same method model_validate but the only problem is, we can't refer to the type in a dynamic way and just say "a unknown basemodel" like this.

And, what's more, for each type we want it to handle we have to repeat this same logic...

Step in TypeVar... TypeVar allows us to specify a variable for a Type, as a opposed to defining a type as a type.

If that doesn't make sense, think of it this way:

# this is a type stored in a variable
my_model_as_a_type = MyModel

# this is an instance stored in a variable
mymodel_as_an_instance = MyModel()

As you can see, a type is literally the thing that defines what that object looks like, but it cannot be used as that object, because it is not an instance of it.

An instance is that thing, initialised in memory, with all the functions and whatever else that implementation of its type has defined.

So, moving onto typevar... how can we improve our code then?

Well, with a few simple changes we can make our function accept any basemodel, but not explicitly say it has to be BaseModel itself, e.g. can be a child or whatever and that the caller of the function can see the type returned to them...

class MyModel(BaseModel):
    ...

class MyModel2(BaseModel):
    ...

T = TypeVar("T", bound=BaseModel)

class MyClass:
    def get_records(
        model_type: Type[T],
        collection_path: str
    ) -> Generator[T, None, None]:
        adapter = TypeAdapter(model_type)
        for record in self.client.collection(collection_path).where(...).stream(...):
            body = record.to_dict()
            if body:
                yield adapter.validate_python(body)

Now when you pass in a model to this function, the object you get back will be an instance from the firestore collection of the type you gave it. And typecheckers are happy, and your colleagues are happy because now they know what the type being returned is, instead of it "possibly" being a union and the output being a "variable" of whatever model happens to be passed in and leaving it to the caller to then figure out which of the union types it is, you can have functions that are defined to only operate on specific collections for specific types...

So we could then have get_my_model_records and get_my_model_2_records function that yield the results of get_records as another generator, and merely provide their specific types to it and whatever else specific implementation details you need, meaning the consumers of your API for these two methods, know exactly what objects they will get back when they call these functions and can easily see that each method is specific for each type.

🌐
Medium
medium.com › pythoneers › understanding-typevar-in-python-f78e5108471d
Understanding TypeVar in Python. A Quick Guide to Generics, Best… | by Harshit Singh | The Pythoneers | Medium
January 8, 2025 - Among these, TypeVar is a powerful tool that helps developers create flexible and reusable code through generics. In this article, we’ll explore what TypeVar is, how it’s used, its relationship with generics, and why it’s not inheritance. We’ll also touch on best practices for using TypeVar, and the changes introduced in Python 3.13.
🌐
Andy Pearce
andy-pearce.com › blog › posts › 2023 › Dec › whats-new-in-python-312-type-hint-improvements
What’s New in Python 3.12 - Type Hint Improvements
December 3, 2023 - Any parameter specifications (ParamSpec that I covered in 3.10) is always invariant. Old-style TypeVar declarations maintain the same semantics, unless you add the new keyword parameter infer_variance=True.