What Are Type Hints in Python?

A photo of some coins as a reference to the coin flip function discussed in the article. The title of the article is overlayed.

Generally, people think of Python as a messy language because it lacks explicit typing and static type checking. But, that’s not quite true in modern times. Surely, we can take advantage of type hints!

Table of Contents

Concept Overview

Most modern programming languages have some form of type system. The idea being that data can be categorized and manipulated based on its type. Of course, every programming language seemingly has a different type system, so the way data types are managed and handled are also different.

In Python, the type system is often described as “duck typing,” falling from that idea that “if it walks like a duck and quacks like a duck, it must be a duck.” What that means in the context of Python is that we never have to explicitly type our variables. As long as the variable behaves like a particular type, it must be that type.

Of course, if you come from a more explicit typing background like Java or C#, then you may find Python difficult to use. After all, it can be difficult to tell what type a variable is at a glance. Fortunately, you’re not forced to live without explicit types in Python. Instead, you can make use of type hints.

As the name suggests, a type hint allows you to document the type of a variable directly through Python syntax. For example, we might have a silly function which flips a coin and lets the user know if they selected the correct side:

import random

def flip_coin(side):
  flip = "heads" if random.random() > .5 else "tails" 
  if side == flip:
    print("Congrats! You win!")
  else:
    print("Unlucky")

While it’s probably clear what type side and flip are at a glance, it’s better to be a bit more explicit by adding some type hints:

import random

def flip_coin(side: str):
  flip: str = "heads" if random.random() > .5 else "tails" 
  if side == flip:
    print("Congrats! You win!")
  else:
    print("Unlucky")

Of course, I would probably verify the input is valid too, but the point still stands.

With that said, it’s important to note that type hints do not actually do anything. Their only purpose is to aid with documentation and third-party tooling (e.g., IDEs, linters, etc.). However, those reasons alone should be enough to convince you start using them.

As always, in the rest of this article, we’ll take a look at some questions you might have about type hints.

Which Versions of Python Support Type Hints?

Type hinting is a Python feature that has been slowly maturing since they were introduced in Python 3.5 via PEP 484Opens in a new tab.—roughly a decade ago. Prior to Python 3.5, the only way to get proper static type checking with tooling was to follow that tool’s conventions.

At the time, type hints in Python 3.5 were very limited to function parameters and return types. Therefore, to get variable-level type hints, you had to make use of comments:

numbers = []  # type: List[int]

Fortunately, in Python 3.6 via PEP 526Opens in a new tab., the type hint syntax was extended to allow inline type hints for variables:

numbers: List[int] = []

Unfortunately, these were still a little clunky because Python couldn’t handle many of the built-in types. As a result, to get the behavior shown above, the List type would have to be imported:

from typing import List

This was fortunately changed in Python 3.9 via PEP 585Opens in a new tab., so the following is now possible:

numbers: list[int] = []

It was a subtle change but a welcome one for folks who wanted to make use of type hinting.

Similarly, you may recall in the flip_coin example that we could specify that the parameter must be a string. Well, since Python 3.8 via PEP 586Opens in a new tab., we can now specify exactly what literal values we will accept:

from typing import Literal

def flip_coin(side: Literal["heads", "tails"]): ...

By Python 3.10, a host of wonderful features were added. For example, courtesy of PEP 604Opens in a new tab., if a function input can take on multiple types, then you can specify that using the union operator:

def busy_func(data: int | str | list): ...

This is particularly handy in situations where you want a parameter to be optional, which can be specified in one of two ways (of course, keep in mind that one requires an explicit import):

def optional_params1(data: Optional[int]): ...
def optional_params2(data: int | None): ...

Until Python 3.11, type hints were still clunky in a few areas. For example, if you wanted to reference the type of the class you were defining inside that class (i.e., self-referential), most type checkers would say that the type was not yet defined. As a result, a string would have to be used as a forward reference:

class Node:
  def connect(next: "Node"): ...

Obviously, this is sort of clunky, so PEP 673Opens in a new tab. included the Self type:

from typing import Self

class Node:
  def connect(next: Self): ...

By Python 3.12, folks were realizing the power of the type system, but it had one major drawback. For sufficiently complicated types, APIs would be littered with type hints. To solve that problem PEP 695Opens in a new tab. introduced a way of creating better type aliases using the type keyword. Now, if you have a situation where you have messy nested structures, you can capture that structure in an alias:

type DictMatrix = list[list[dict[str, str]]]

def get_game_grid() -> DictMatrix: ...

Ultimately, I expect the type hinting system to continue to mature. Fortunately, even if this article goes out of date, Python lists all of their changes to the type system for your perusalOpens in a new tab..

What If I Am Not On the Latest Version of Python?

While type hints are already a decade old at this point, the type hinting system receives annual updates. This means that you may not get access to the latest features if you’re on an older version of Python. By convention, it is a good idea to update your Python version regularly. After all, the Python community only supports about five versions of Python at a time. At the time of writing, the currently supported versions are 3.9, 3.10, 3.11, 3.12, and 3.13. 3.9 will reach end of life right around the time 3.14 is released (i.e., October 2025).

With that said, if you absolutely cannot update your version of Python, modern type hinting can be accessed by “importing” it directly. The trick Python developers use for this is the following:

from __future__ import annotations

According to the docsOpens in a new tab., this is known as a future statement, and it essentially forces the compiler to support newer syntax and/or semantics. Currently, the only way to execute the future statement above is to be on Python 3.7 or newer. I do not know exactly which type hinting features are included and which are not (maybe this is a nonsensical concern), and I was unable to figure it out by browsing the docs. Feel free to browse PEP 563Opens in a new tab. yourself.

What All Can Be Type Hinted?

While the concept overview may have implied that type hints are only available for variables, they can actually be used in a lot of important locations. For example, function parameters and return types can be labeled:

def sum(a: int, b: int) -> int: ...

For a complete list of all of the typing features, check out the typing APIOpens in a new tab..

Not So Subtle Hints

Ultimately, type hinting is a feature I use basically any time I use Python now. In fact, I am often upset when I look at an API, and there aren’t type hints. It makes it much more difficult to figure out exactly what a function accepts or what it’s going to return. This is particularly true for APIs where I have to run the functions to figure out what they do (e.g., the Canvas API). Meanwhile, APIs like PRAW and Discord.py are much nicer to use because of the type hints.

With that said, I think we’ll call it here for today. As always, if you liked this article, there are plenty more like it:

Likewise, here are some useful Python resources (#ad):

If you want, you can even help support the site in other ways such as through the Patreon or Discord. You’ll just need to head over to our list of ways to grow the site. Otherwise, take care!

The Python Concept Map (15 Articles)—Series Navigation

An activity I regularly do with my students is a concept map. Typically, we do it at the start and end of each semester to get an idea of how well our understanding of the material as matured over time. Naturally, I had the idea to extend this concept into its own series, just to see how deeply I can explore my own knowledge of Python. It should be a lot of fun, and I hope you enjoy it!

Jeremy Grifski

Jeremy grew up in a small town where he enjoyed playing soccer and video games, practicing taekwondo, and trading Pokémon cards. Once out of the nest, he pursued a Bachelors in Computer Engineering with a minor in Game Design. After college, he spent about two years writing software for a major engineering company. Then, he earned a master's in Computer Science and Engineering. Today, he pursues a PhD in Engineering Education in order to ultimately land a teaching gig. In his spare time, Jeremy enjoys spending time with his wife and kid, playing Overwatch 2, Lethal Company, and Baldur's Gate 3, reading manga, watching Penguins hockey, and traveling the world.

Recent Code Posts