Type hinting is a relatively recent addition to Python, so older code, and still most code, doesn't use them at all. Annotated code still needs to work with that existing code, so Python is a gradual system where typed and untyped code mixes together in one program.

When they are used, for most types they are purely structural, saying that certain methods are available but not where they came from, but for some builtins at least (like int) they do convey concrete implementation aspects that could be optimised. There are cases where static type information could usefully help in optimising code execution, but perhaps not as many as you'd expect.

Relying on the annotations would also require that they be correct, which Python makes no guarantee of. Ensuring this correctness needs sound gradual typing, which requires run-time checking of types as well and carries a whole lot of run-time and semantic effects. It would be a really significant change to Python semantics to start enforcing these type annotations in that way, so the informational content of an annotation is even a little bit lower than it looks. In any case, the costs of the dynamic checking to make it sound enough to rely on almost certainly outweigh the performance benefits in almost all cases.

For a language like Python where code making use of very dynamic features and metaprogramming is fairly common, the benefit for most programs likely isn't high enough to be worth the costs no matter what. Even the long-standing nominal typing elements in Python (like collections.abc.Sequence) doesn't convey much about the actual implementation, and mutating classes at run time is not uncommon inside common Python frameworks. These objects need to be usable with all the type-hinted code, so that code needs to deal with a big variety of object shapes. This is all very different to what's typical in C++, where these things might happen at (or before) compile time, if anything.

Instead, just-in-time compilation is a more realistic performance enhancement to Python, which is also not included in the CPython implementation, but is in some alternative versions. For high-performance Python code, as used in some scientific computation, there are both libraries like numpy that keep everything inside, and specialised JIT systems like Numba that optimise specified functions at run time. These are more practical performance targets for Python.

Answer from Michael Homer on Stack Exchange
Top answer
1 of 4
7

Type hinting is a relatively recent addition to Python, so older code, and still most code, doesn't use them at all. Annotated code still needs to work with that existing code, so Python is a gradual system where typed and untyped code mixes together in one program.

When they are used, for most types they are purely structural, saying that certain methods are available but not where they came from, but for some builtins at least (like int) they do convey concrete implementation aspects that could be optimised. There are cases where static type information could usefully help in optimising code execution, but perhaps not as many as you'd expect.

Relying on the annotations would also require that they be correct, which Python makes no guarantee of. Ensuring this correctness needs sound gradual typing, which requires run-time checking of types as well and carries a whole lot of run-time and semantic effects. It would be a really significant change to Python semantics to start enforcing these type annotations in that way, so the informational content of an annotation is even a little bit lower than it looks. In any case, the costs of the dynamic checking to make it sound enough to rely on almost certainly outweigh the performance benefits in almost all cases.

For a language like Python where code making use of very dynamic features and metaprogramming is fairly common, the benefit for most programs likely isn't high enough to be worth the costs no matter what. Even the long-standing nominal typing elements in Python (like collections.abc.Sequence) doesn't convey much about the actual implementation, and mutating classes at run time is not uncommon inside common Python frameworks. These objects need to be usable with all the type-hinted code, so that code needs to deal with a big variety of object shapes. This is all very different to what's typical in C++, where these things might happen at (or before) compile time, if anything.

Instead, just-in-time compilation is a more realistic performance enhancement to Python, which is also not included in the CPython implementation, but is in some alternative versions. For high-performance Python code, as used in some scientific computation, there are both libraries like numpy that keep everything inside, and specialised JIT systems like Numba that optimise specified functions at run time. These are more practical performance targets for Python.

2 of 4
10

Python's typing model is intended for outside checkers to check python code. Pretty much any expression can go in a type hint:

x: print("hi") = 1
print(x)

produces

hi
1

Obviously any typechecker will reject that as bogus, but the Python interpreter has to support it in order to fulfil the Python standard.

Leading on from that, there's no guarantee that the type hints are going to be correct, because by the Python standard, they don't have to be. This is also perfectly valid to an interpreter:

x: str = 1

As for why the Python standard has been designed this way, it's a very long story that I don't know enough of to recount here, but it comes down to "it doesn't fit Python's ideals as a language".

In terms of using types to speed something up, check out mypyc - it uses the fact that the code has to be correctly checked by mypy before it compiles code in order to speed up typed python code significantly. For example, this would be sped up in exactly the manner you've described by mypyc, but it can't be by a normal Python interpreter:

def fib(n: int) -> int:
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)
Discussions

python - How can I specify the function type in my type hints? - Stack Overflow
How can I specify the type hint of a variable as a function type? There is no typing.Function, and I could not find anything in the relevant PEP, PEP 483. More on stackoverflow.com
๐ŸŒ stackoverflow.com
Why Type Hinting Sucks!
If you have a super-generic function like that and type hinting enforced, you just use Any and don't care about it. It's better than not type hinting your codebase at all, as in 99% of cases you can use the proper hints. Working in a big python codebase without type hints is a huge PIA. More on reddit.com
๐ŸŒ r/Python
290
935
February 13, 2023
How to Use Type Hints for Multiple Return Types in Python : Python
The official Python community for Reddit! Stay up to date with the latest news, packages, and meta information relating to the Python programming... More on old.reddit.com
๐ŸŒ r/Python
NumPy type hints?

my_var = np.uint8(10) ?

More on reddit.com
๐ŸŒ r/Python
7
2
July 14, 2018
๐ŸŒ
Python documentation
docs.python.org โ€บ 3 โ€บ library โ€บ typing.html
typing โ€” Support for type hints
1 week ago - Source code: Lib/typing.py This module provides runtime support for type hints. Consider the function below: The function surface_area_of_cube takes an argument expected to be an instance of float,...
๐ŸŒ
Ryan Himmelwright
ryan.himmelwright.net โ€บ post โ€บ python-type-hinting-intro
Intro to Python Type Hinting | ฮป ryan.himmelwright.net
January 29, 2022 - To use type hinting in python, just specify the type when defining variables using the format var_name: type. Additionally, to define a return type for a function, add a -> symbol at the end of the function def pointing to the return type.
๐ŸŒ
GeeksforGeeks
geeksforgeeks.org โ€บ python โ€บ type-hints-in-python
Type Hints in Python - GeeksforGeeks
May 3, 2025 - Type hints are a feature in Python that allow developers to annotate their code with expected types for variables and function arguments.
Find elsewhere
๐ŸŒ
Reddit
reddit.com โ€บ r/python โ€บ why type hinting sucks!
r/Python on Reddit: Why Type Hinting Sucks!
February 13, 2023 -

Type hints are great! But I was playing Devil's advocate on a thread recently where I claimed actually type hinting can be legitimately annoying, especially to old school Python programmers.

But I think a lot of people were skeptical, so let's go through a made up scenario trying to type hint a simple Python package. Go to the end for a TL;DR.

The scenario

This is completely made up, all the events are fictitious unless explicitly stated otherwise (also editing this I realized attempts 4-6 have even more mistakes in them than intended but I'm not rewriting this again):

You maintain a popular third party library slowadd, your library has many supporting functions, decorators, classes, and metaclasses, but your main function is:

def slow_add(a, b):
    time.sleep(0.1)
    return a + b

You've always used traditional Python duck typing, if a and b don't add then the function throws an exception. But you just dropped support for Python 2 and your users are demanding type hinting, so it's your next major milestone.

First attempt at type hinting

You update your function:

def slow_add(a: int, b: int) -> int:
    time.sleep(0.1)
    return a + b

All your tests pass, mypy passes against your personal code base, so you ship with the release note "Type Hinting Support added!"

Second attempt at type hinting

Users immediately flood your GitHub issues with complaints! MyPy is now failing for them because they pass floats to slow_add, build processes are broken, they can't downgrade because of internal Enterprise policies of always having to increase type hint coverage, their weekend is ruined from this issue.

You do some investigating and find that MyPy supports Duck type compatibility for ints -> floats -> complex. That's cool! New release:

def slow_add(a: complex, b: complex) -> complex:
    time.sleep(0.1)
    return a + b

Funny that this is a MyPy note and not a PEP standard...

Third attempt at type hinting

Your users thank you for your quick release, but a couple of days later one user asks why you no longer support Decimal. You replace complex with Decimal but now your other MyPy tests are failing.

You remember Python 3 added Numeric abstract base classes, what a perfect use case, just type hint everything as numbers.Number.

Hmmm, MyPy doesn't consider any of integers, or floats, or Decimals to be numbers :(.

After reading through typing you guess you'll just Union in the Decimals:

def slow_add(
    a: Union[complex, Decimal], b: Union[complex, Decimal]
) -> Union[complex, Decimal]:
    time.sleep(0.1)
    return a + b

Oh no! MyPy is complaining that you can't add your other number types to Decimals, well that wasn't your intention anyway...

More reading later and you try overload:

@overload
def slow_add(a: Decimal, b: Decimal) -> Decimal:
    ...

@overload
def slow_add(a: complex, b: complex) -> complex:
    ...

def slow_add(a, b):
    time.sleep(0.1)
    return a + b

But MyPy on strict is complaining that slow_add is missing a type annotation, after reading this issue you realize that @overload is only useful for users of your function but the body of your function will not be tested using @overload. Fortunately in the discussion on that issue there is an alternative example of how to implement:

T = TypeVar("T", Decimal, complex)

def slow_add(a: T, b: T) -> T:
    time.sleep(0.1)
    return a + b

Fourth attempt at type hinting

You make a new release, and a few days later more users start complaining. A very passionate user explains the super critical use case of adding tuples, e.g. slow_add((1, ), (2, ))

You don't want to start adding each type one by one, there must be a better way! You learn about Protocols, and Type Variables, and positional only parameters, phew, this is a lot but this should be perfect now:

T = TypeVar("T")

class Addable(Protocol):
    def __add__(self: T, other: T, /) -> T:
        ...

def slow_add(a: Addable, b: Addable) -> Addable:
    time.sleep(0.1)
    return a + b

A mild diversion

You make a new release noting "now supports any addable type".

Immediately the tuple user complains again and says type hints don't work for longer Tuples: slow_add((1, 2), (3, 4)). That's weird because you tested multiple lengths of Tuples and MyPy was happy.

After debugging the users environment, via a series of "back and forth"s over GitHub issues, you discover that pyright is throwing this as an error but MyPy is not (even in strict mode). You assume MyPy is correct and move on in bliss ignoring there is actually a fundamental mistake in your approach so far.

(Author Side Note - It's not clear if MyPy is wrong but it defiantly makes sense for Pyright to throw an error here, I've filed issues against both projects and a pyright maintainer has explained the gory details if you're interested. Unfortunately this was not really addressed in this story until the "Seventh attempt")

Fifth attempt at type hinting

A week later a user files an issue, the most recent release said that "now supports any addable type" but they have a bunch of classes that can only be implemented using __radd__ and the new release throws typing errors.

You try a few approaches and find this seems to best solve it:

T = TypeVar("T")

class Addable(Protocol):
    def __add__(self: T, other: T, /) -> T:
        ...

class RAddable(Protocol):
    def __radd__(self: T, other: Any, /) -> T:
        ...

@overload
def slow_add(a: Addable, b: Addable) -> Addable:
    ...

@overload
def slow_add(a: Any, b: RAddable) -> RAddable:
    ...

def slow_add(a: Any, b: Any) -> Any:
    time.sleep(0.1)
    return a + b

Annoyingly there is now no consistent way for MyPy to do anything with the body of the function. Also you weren't able to fully express that when b is "RAddable" that "a" should not be the same type because Python type annotations don't yet support being able to exclude types.

Sixth attempt at type hinting

A couple of days later a new user complains they are getting type hint errors when trying to raise the output to a power, e.g. pow(slow_add(1, 1), slow_add(1, 1)). Actually this one isn't too bad, you quick realize the problem is your annotating Protocols, but really you need to be annotating Type Variables, easy fix:

T = TypeVar("T")

class Addable(Protocol):
    def __add__(self: T, other: T, /) -> T:
        ...

A = TypeVar("A", bound=Addable)

class RAddable(Protocol):
    def __radd__(self: T, other: Any, /) -> T:
        ...

R = TypeVar("R", bound=RAddable)

@overload
def slow_add(a: A, b: A) -> A:
    ...

@overload
def slow_add(a: Any, b: R) -> R:
    ...

def slow_add(a: Any, b: Any) -> Any:
    time.sleep(0.1)
    return a + b

Seventh attempt at type hinting

Tuple user returns! He says MyPy in strict mode is now complaining with the expression slow_add((1,), (2,)) == (1, 2) giving the error:

Non-overlapping equality check (left operand type: "Tuple[int]", right operand type: "Tuple[int, int]")

You realize you can't actually guarantee anything about the return type from some arbitrary __add__ or __radd__, so you starting throwing Any Liberally around:

class Addable(Protocol):
    def __add__(self: "Addable", other: Any, /) -> Any:
        ...

class RAddable(Protocol):
    def __radd__(self: "RAddable", other: Any, /) -> Any:
        ...

@overload
def slow_add(a: Addable, b: Any) -> Any:
    ...

@overload
def slow_add(a: Any, b: RAddable) -> Any:
    ...

def slow_add(a: Any, b: Any) -> Any:
    time.sleep(0.1)
    return a + b

Eighth attempt at type hinting

Users go crazy! The nice autosuggestions their IDE provided them in the previous release have all gone! Well you can't type hint the world, but I guess you could include type hints for the built-in types and maybe some Standard Library types like Decimal:

You think you can rely on some of that MyPy duck typing but you test:

@overload
def slow_add(a: complex, b: complex) -> complex:
    ...

And realize that MyPy throws an error on something like slow_add(1, 1.0).as_integer_ratio(). So much for that nice duck typing article on MyPy you read earlier.

So you end up implementing:

class Addable(Protocol):
    def __add__(self: "Addable", other: Any, /) -> Any:
        ...

class RAddable(Protocol):
    def __radd__(self: "RAddable", other: Any, /) -> Any:
        ...

@overload
def slow_add(a: int, b: int) -> int:
    ...

@overload
def slow_add(a: float, b: float) -> float:
    ...

@overload
def slow_add(a: complex, b: complex) -> complex:
    ...

@overload
def slow_add(a: str, b: str) -> str:
    ...

@overload
def slow_add(a: tuple[Any, ...], b: tuple[Any, ...]) -> tuple[Any, ...]:
    ...

@overload
def slow_add(a: list[Any], b: list[Any]) -> list[Any]:
    ...

@overload
def slow_add(a: Decimal, b: Decimal) -> Decimal:
    ...

@overload
def slow_add(a: Fraction, b: Fraction) -> Fraction:
    ...

@overload
def slow_add(a: Addable, b: Any) -> Any:
    ...

@overload
def slow_add(a: Any, b: RAddable) -> Any:
    ...

def slow_add(a: Any, b: Any) -> Any:
    time.sleep(0.1)
    return a + b

As discussed earlier MyPy doesn't use the signature of any of the overloads and compares them to the body of the function, so all these type hints have to manually validated as accurate by you.

Ninth attempt at type hinting

A few months later a user says they are using an embedded version of Python and it hasn't implemented the Decimal module, they don't understand why your package is even importing it given it doesn't use it. So finally your code looks like:

from __future__ import annotations

import time
from typing import TYPE_CHECKING, Any, Protocol, TypeVar, overload

if TYPE_CHECKING:
    from decimal import Decimal
    from fractions import Fraction


class Addable(Protocol):
    def __add__(self: "Addable", other: Any, /) -> Any:
        ...

class RAddable(Protocol):
    def __radd__(self: "RAddable", other: Any, /) -> Any:
        ...

@overload
def slow_add(a: int, b: int) -> int:
    ...

@overload
def slow_add(a: float, b: float) -> float:
    ...

@overload
def slow_add(a: complex, b: complex) -> complex:
    ...

@overload
def slow_add(a: str, b: str) -> str:
    ...

@overload
def slow_add(a: tuple[Any, ...], b: tuple[Any, ...]) -> tuple[Any, ...]:
    ...

@overload
def slow_add(a: list[Any], b: list[Any]) -> list[Any]:
    ...

@overload
def slow_add(a: Decimal, b: Decimal) -> Decimal:
    ...

@overload
def slow_add(a: Fraction, b: Fraction) -> Fraction:
    ...

@overload
def slow_add(a: Addable, b: Any) -> Any:
    ...

@overload
def slow_add(a: Any, b: RAddable) -> Any:
    ...

def slow_add(a: Any, b: Any) -> Any:
    time.sleep(0.1)
    return a + b

TL;DR

Turning even the simplest function that relied on Duck Typing into a Type Hinted function that is useful can be painfully difficult.

Please always put on your empathetic hat first when asking someone to update their code to how you think it should work.

In writing up this post I learnt a lot about type hinting, please try and find edge cases where my type hints are wrong or could be improved, it's a good exercise.

Edit: Had to fix a broken link.

Edit 2: It was late last night and I gave up on fixing everything, some smart people nicely spotted the errors!

I have a "tenth attempt" to address these error. But pyright complains about it because my overloads overlap, however I don't think there's a way to express what I want in Python annotations without overlap. Also Mypy complains about some of the user code I posted earlier giving the error comparison-overlap, interestingly though pyright seems to be able to detect here that the types don't overlap in the user code.

I'm going to file issues on pyright and mypy, but fundamentally they might be design choices rather than strictly bugs and therefore a limit on the current state of Python Type Hinting:

T = TypeVar("T")

class SameAddable(Protocol):
    def __add__(self: T, other: T, /) -> T:
        ...

class Addable(Protocol):
    def __add__(self: "Addable", other: Any, /) -> Any:
        ...

class SameRAddable(Protocol):
    def __radd__(self: T, other: Any, /) -> T:
        ...

class RAddable(Protocol):
    def __radd__(self: "RAddable", other: Any, /) -> Any:
        ...

SA = TypeVar("SA", bound=SameAddable)
RA = TypeVar("RA", bound=SameRAddable)


@overload
def slow_add(a: SA, b: SA) -> SA:
    ...

@overload
def slow_add(a: Addable, b: Any) -> Any:
    ...

@overload
def slow_add(a: Any, b: RA) -> RA:
    ...

@overload
def slow_add(a: Any, b: RAddable) -> Any:
    ...

def slow_add(a: Any, b: Any) -> Any:
    time.sleep(0.1)
    return a + b
๐ŸŒ
Julianhysi
julianhysi.com โ€บ post โ€บ 15
Julian's Blog - Type hints and Modern Python
Type annotations were introduced to Python in the 3.5 release in late 2015. They're also often referred to as "type hints", and even though the two concepts are not identical (you can practice hinting even by placing comments or naming your object my_list), we'll use the two terms interchangeably ...
๐ŸŒ
Python
peps.python.org โ€บ pep-0484
PEP 484 โ€“ Type Hints | peps.python.org
The string literal should contain a valid Python expression (i.e., compile(lit, '', 'eval') should be a valid code object) and it should evaluate without errors once the module has been fully loaded. The local and global namespace in which it is evaluated should be the same namespaces in which default arguments to the same function would be evaluated. Moreover, the expression should be parseable as a valid type hint, i.e., it is constrained by the rules from the section Acceptable type hints above.
๐ŸŒ
Medium
medium.com โ€บ geekculture โ€บ type-hinting-easy-coding-add-types-info-in-your-python-codes-db569a5b4618
Type hinting, Easy coding (Add types info in your Python codes) | by Liu Zheng | Geek Culture | Medium
July 1, 2021 - However, this line doesnโ€™t make sense because its default value None itself is not of type int. (Note that if you do this and call the function, you codes will run! Python itself doesnโ€™t really check the types for you. As the name suggests, they are just type hints, not type declaration.
๐ŸŒ
Dagster
dagster.io โ€บ blog โ€บ python-type-hinting
Using Type Hinting in Python Projects
However, this dynamic nature can ... as a part of the standard library via PEP 484, allow you to specify the expected type of a variable, function parameter, or return value....
๐ŸŒ
Mypy
mypy.readthedocs.io โ€บ en โ€บ stable โ€บ cheat_sheet_py3.html
Type hints cheat sheet - mypy 1.19.1 documentation
# For most types, just use the name of the type in the annotation # Note that mypy can usually infer the type of a variable from its value, # so technically these annotations are redundant x: int = 1 x: float = 1.0 x: bool = True x: str = "test" x: bytes = b"test" # For collections on Python 3.9+, the type of the collection item is in brackets x: list[int] = [1] x: set[int] = {6, 7} # For mappings, we need the types of both keys and values x: dict[str, float] = {"field": 2.0} # Python 3.9+ # For tuples of fixed size, we specify the types of all the elements x: tuple[int, str, float] = (3, "yes
๐ŸŒ
FastAPI
fastapi.tiangolo.com โ€บ python-types
Python Types Intro - FastAPI
These "type hints" or annotations are a special syntax that allow declaring the type of a variable. By declaring types for your variables, editors and tools can give you better support.
๐ŸŒ
Better Programming
betterprogramming.pub โ€บ pythons-type-hinting-friend-foe-or-just-a-headache-73c7849039c7
Python's Type Hinting: Friend, Foe, or Just a Headache?
November 29, 2022 - It started back in Python 3.5, and ever since, this functionality has been getting more types and functions, andโ€” in Python 3.10 โ€” even a new operator, |. Fortunately or not, this operator has a similar meaning to the same operator used for sets. Iโ€™ve heard varying opinions on type hinting, ranging from rage and hatred to love and devotion.
๐ŸŒ
DERLIN.
blog.derlin.ch โ€บ python-type-hints-and-future-annotations
Python, type hints, and future annotations
February 2, 2024 - In 2006, Python 3 introduced a syntax for adding arbitrary metadata annotations to Python functions (PEP 3107: Function Annotations). (Ab)used by many 3rd parties in different ways, Python 3.5 finally formalized the semantics in PEP 484: Type Hints (Python 3.5, October 2014 - see also PEP 483 โ€“ The Theory of Type Hints) and added the typing module to the standard library.
๐ŸŒ
JetBrains
jetbrains.com โ€บ help โ€บ pycharm โ€บ type-hinting-in-product.html
Type hinting in PyCharm | PyCharm Documentation
October 21, 2025 - Select Add type hint for .... Press Enter to complete the action or edit the type if appropriate. You can also use Python stubs to specify the types of variables, functions, and class fields.
๐ŸŒ
Real Python
realpython.com โ€บ ref โ€บ glossary โ€บ type-hint
type hint | Python Glossary โ€“ Real Python
In Python, a type hint is a syntactic construct that allows you to indicate the expected data types of variables, function arguments, and return values.
๐ŸŒ
Towards Data Science
towardsdatascience.com โ€บ home โ€บ latest โ€บ type hints in python
Type Hints in Python | Towards Data Science
January 16, 2025 - Type hints indicate the datatypes of both the inputs and outputs in functions (it applies to class methods as well). A problem many Python users complain about is the freedom we have to change a variable type.
๐ŸŒ
Real Python
realpython.com โ€บ python-type-hints-multiple-types
How to Use Type Hints for Multiple Return Types in Python โ€“ Real Python
March 8, 2024 - In Python, type hinting is an optional yet useful feature to make your code easier to read, reason about, and debug. With type hints, you let other developers know the expected data types for variables, function arguments, and return values.