Type Hints
Learn how to use Python type hints to annotate variables and functions, improving editor support and catching bugs early without changing runtime behavior.
Introduction
Python is dynamically typed, meaning you never have to declare a variable's type before using it. This is great for quick scripts, but in larger codebases it can become hard to know what type of value a function expects or returns just by reading its signature.
Type hints let you add optional type information to your code. They do not change how Python runs your program, but they make your intentions explicit to readers, editors, and tools.
- What type hints are and why they are optional.
- How to hint variables, function parameters, and return types.
- How to hint collection types like list[int] and dict[str, int].
- How to represent values that might be None using Optional and | None.
- How tools like mypy use type hints to catch bugs before runtime.
What Are Type Hints?
A type hint is a piece of annotation you add to a variable, function parameter, or return value, indicating what type of data is expected. They were introduced to help document and check code without changing Python's dynamic nature.
def greet(name: str) -> str:
return f"Hello, {name}!"
print(greet("Alex"))Hello, Alex!Here, name: str tells readers (and tools) that name should be a string, and -> str indicates the function returns a string.
Type Hints Are Optional and Not Enforced
Unlike statically-typed languages such as Java or C++, Python does not check type hints while your program runs. They are purely documentation and tooling aids — Python will happily run code that violates them.
def greet(name: str) -> str:
return f"Hello, {name}!"
print(greet(123)) # Wrong type, but Python does not stop thisHello, 123!Passing the "wrong" type will not raise an error by itself — Python ignores type hints when actually executing code. Catching these mismatches requires a separate static type checker like mypy, covered later in this lesson.
Hinting Variables
You can add a type hint directly to a variable assignment using a colon after the variable name.
age: int = 16
name: str = "Alex"
height: float = 5.9
is_student: bool = True
print(age, name, height, is_student)16 Alex 5.9 TrueThese hints are optional for variables — most Python code only bothers hinting function signatures, since the variable's type is often obvious from its assigned value.
Hinting Functions
Function hints are where type hints are most valuable, since they document exactly what a function expects and what it gives back — without needing to read the function's implementation.
def add(a: int, b: int) -> int:
return a + b
def describe(name: str, age: int) -> str:
return f"{name} is {age} years old."
print(add(4, 5))
print(describe("Maya", 15))9
Maya is 15 years old.Hinting Collections
Modern Python lets you hint the contents of collections like lists and dictionaries using square brackets, so both the container and its contents are documented.
def total_score(scores: list[int]) -> int:
return sum(scores)
def get_ages(people: dict[str, int]) -> list[int]:
return list(people.values())
print(total_score([88, 92, 79]))
print(get_ages({"Alex": 16, "Maya": 15}))259
[16, 15]list[int]
A list containing only integers.
dict[str, int]
A dictionary with string keys and integer values.
tuple[int, str]
A tuple with a fixed structure: an int followed by a str.
set[str]
A set containing only strings.
Optional and | None
Sometimes a value might legitimately be None — for example, a function that searches for something and may not find it. Python offers two equivalent ways to express "this type, or None."
from typing import Optional
def find_user(user_id: int) -> Optional[str]:
users = {1: "Alex", 2: "Maya"}
return users.get(user_id)
# Modern equivalent (Python 3.10+):
def find_user_modern(user_id: int) -> str | None:
users = {1: "Alex", 2: "Maya"}
return users.get(user_id)
print(find_user(1))
print(find_user(99))Alex
NoneOptional[str] and str | None mean exactly the same thing: the value is either a string or None. The | None syntax was added in Python 3.10 and is now the more common, concise style.
Static Checking with mypy
Since Python ignores type hints at runtime, catching mismatches requires a separate static type checker. The most popular one is mypy, which scans your code and reports type errors without ever running it.
def add(a: int, b: int) -> int:
return a + b
add("4", "5") # Passing strings instead of intsmypy checked.pychecked.py:4: error: Argument 1 to "add" has incompatible type "str"; expected "int"mypy is installed separately with pip install mypy and is typically run as part of a project's testing or CI process, catching type errors before the code ever runs in production.
Why Type Hints Help
Better Autocomplete
Editors like VS Code use type hints to suggest accurate methods and properties as you type.
Catch Bugs Early
Tools like mypy catch type mismatches before the code ever runs.
Self-Documenting Code
Function signatures explain what is expected without reading the implementation.
Easier Collaboration
Teammates understand function contracts faster in large codebases.
Common Mistakes
- Believing type hints will raise an error at runtime — they do not, by themselves.
- Forgetting to import Optional from typing when using the older syntax.
- Mixing up list[int] (Python 3.9+) with older typing.List[int] unnecessarily in new code.
- Adding hints but never actually running a checker like mypy to benefit from them.
- Over-hinting trivial local variables where the type is already obvious.
Best Practices
- Hint function parameters and return types, especially in shared or larger codebases.
- Use built-in generics like list[int] and dict[str, int] in modern Python.
- Use str | None (or Optional[str]) whenever a value might legitimately be missing.
- Run a static checker like mypy regularly, not just once.
- Keep hints simple and readable — do not over-engineer types for small scripts.
Frequently Asked Questions
Do type hints make Python statically typed?
No. Python remains dynamically typed at runtime. Type hints are purely optional annotations checked by external tools, not the Python interpreter itself.
Will my code break if I add incorrect type hints?
No, Python ignores type hints when running your code, so incorrect hints will not cause runtime errors — but they will mislead readers and tools like mypy.
Is mypy required to use type hints?
No, type hints are useful on their own for documentation and editor autocomplete, but mypy (or similar tools) is needed to actually catch type errors before runtime.
Should I add type hints to every single variable?
Not necessarily. Function signatures benefit the most; hinting every obvious local variable often adds noise without much benefit.
What Python version do I need for list[int] syntax?
Built-in generic syntax like list[int] and dict[str, int] works natively from Python 3.9 onward; earlier versions require importing List and Dict from the typing module.
Key Takeaways
- Type hints document expected types but are not enforced at runtime.
- Function parameters and return types are hinted like def greet(name: str) -> str:.
- Collection types can be hinted with list[int] and dict[str, int].
- Optional[str] and str | None both represent a value that might be None.
- Tools like mypy perform static type checking to catch mismatches before running code.
Summary
Type hints add optional, non-enforced type information to Python code, making function contracts explicit and helping both humans and tools understand what a piece of code expects and returns.
In this lesson, you learned the basic syntax for hinting variables and functions, how to hint collections and optional values, and how a tool like mypy performs static checking to catch bugs before your code ever runs.
- You understand that type hints are optional and not enforced at runtime.
- You can hint variables, function parameters, and return types.
- You can hint collections and Optional/None values.
- You are ready to learn about context managers.