Type Hints
Python does not make you declare types. A variable can hold a number now and a string later. That freedom is great for quick scripts, but in bigger programs it becomes hard to remember what a function expects. Does make_bill() want the price as a number or as text? Does it return a string or a float?
Type hints answer those questions inside the code. They are notes like price: float that tell readers, editors and checking tools what type a value should have. Modern Python code uses them everywhere, and so will the rest of this course.
Annotating variables and functions
Write a colon and a type after a variable or parameter name. Write -> and a type before the colon of a def line to describe the return value.
city: str = "Kochi"
pin_code: int = 682001
rating: float = 4.6
is_open: bool = True
def add_gst(price: float, rate: int = 18) -> float:
return round(price * (100 + rate) / 100, 2)
def greet(name: str) -> None:
print(f"Namaste, {name}")
print(add_gst(1299, 5))
greet("Meera")
print(add_gst.__annotations__)1363.95
Namaste, Meera
{'price': <class 'float'>, 'rate': <class 'int'>, 'return': <class 'float'>}-> None means the function returns nothing useful. Python stores the hints in a special attribute called __annotations__, which tools can read. An int is accepted wherever a float is expected, so add_gst(1299, 5) is fine.
Inside a function you rarely need to annotate local variables: checkers work out that total = 0 is an int on their own. Annotate function parameters and return values first; they give the most benefit.
Hints are not enforced at runtime
This surprises many beginners: Python itself ignores type hints when running your code. A wrong type does not raise an error at the moment of the call.
def total_marks(marks: list[int]) -> int:
return sum(marks)
print(total_marks([78, 85, 91]))
print(total_marks((1.5, 2.5))) # wrong type, but Python runs it anyway254 4.0
To catch such mistakes you use a type checker, a separate program that reads your code without running it. The most popular are mypy and pyright (pyright also powers the Python support in VS Code). After installing one with pip (see pip & Virtual Environments), you run, for example, mypy marks.py, and it reports that the second call passes a tuple of floats where a list of integers is expected. Your editor shows the same warnings as red underlines while you type.
Lists, dictionaries and other collections
For collections, put the type of the items in square brackets after the built-in name. This works directly on list, dict, set and tuple; you do not need to import anything.
| Hint | Meaning |
|---|---|
list[str] | A list of strings |
dict[str, int] | A dictionary with string keys and integer values |
set[int] | A set of integers |
tuple[str, int] | A tuple of exactly two items: a string, then an integer |
tuple[float, ...] | A tuple of any length, all floats |
def class_topper(results: dict[str, int]) -> tuple[str, int]:
name = max(results, key=results.get)
return name, results[name]
def roll_numbers(names: list[str], start: int = 1) -> dict[int, str]:
return {number: name for number, name in enumerate(names, start=start)}
print(class_topper({"Aarav": 82, "Priya": 94, "Kabir": 77}))
print(roll_numbers(["Isha", "Rohan", "Ananya"], start=101))('Priya', 94)
{101: 'Isha', 102: 'Rohan', 103: 'Ananya'}You may see List[str] and Dict[str, int] imported from the typing module in older code. Since Python 3.9 the lowercase built-in names are preferred.
Optional values with X | None
Many functions return a value or None when nothing is found. Write that with the | operator, which means "or": str | None. The same operator combines any types, as in int | float.
def find_phone(contacts: dict[str, str], name: str) -> str | None:
return contacts.get(name)
contacts = {"Farhan": "98100 12345", "Isha": "99990 54321"}
for person in ["Isha", "Kabir"]:
phone = find_phone(contacts, person)
if phone is None:
print(f"No number saved for {person}")
else:
print(f"{person}: {phone}")Isha: 99990 54321 No number saved for Kabir
A type checker will now warn you if you use the result as a string without first checking for None, which prevents a very common crash. Older code writes Optional[str] from the typing module, which means the same as str | None.
Two more useful names: Any from typing turns checking off for a value, and Callable from collections.abc describes a function, for example Callable[[float], float] for a function that takes a float and returns a float.
Type aliases and generics
When a hint gets long or carries meaning, give it a name with the type statement (Python 3.12 and later). This creates a type alias:
type Rupees = int
type Menu = dict[str, Rupees]
def bill(menu: Menu, order: list[str]) -> Rupees:
return sum(menu[item] for item in order)
canteen: Menu = {"samosa": 15, "chai": 12, "poha": 30}
print(bill(canteen, ["chai", "samosa", "chai"]))
print(Rupees.__value__)39 <class 'int'>
A generic function works with many types but keeps them connected. In first_item below, [T] after the name introduces a type variable T. The hint says "you give me a list of some type T, and I return a T". If you pass a list of strings, the checker knows the result is a string.
def first_item[T](items: list[T]) -> T:
return items[0]
class Shelf[T]:
def __init__(self) -> None:
self.items: list[T] = []
def put(self, item: T) -> None:
self.items.append(item)
print(first_item(["Pune", "Jaipur"]).upper())
print(first_item([4, 9, 2]) + 1)
books: Shelf[str] = Shelf()
books.put("Godan")
print(books.items)PUNE 5 ['Godan']
class Shelf[T]: makes a generic class in the same way. This square-bracket syntax arrived in Python 3.12; older code creates type variables with T = TypeVar("T").
Common mistakes
- Expecting Python to reject wrong types. Hints are not checked at runtime. Run mypy or pyright, or validate values yourself.
- Writing
list(int)orlist<int>. The syntax is square brackets:list[int]. - Using
Noneas a default without saying so.def f(limit: int = None)is wrong; writelimit: int | None = None. - Annotating everything. Focus on function signatures and class attributes; let the checker infer simple local variables.
Key points
- Hints describe types:
name: str,def f(x: int) -> str:,-> Nonefor no result. - Python ignores hints at runtime; checkers such as mypy and pyright and your editor use them to find bugs.
- Use built-in generics like
list[int],dict[str, float]andtuple[str, int]. X | Nonemarks values that may be missing;|also combines types such asint | float.- The
typestatement creates aliases, anddef f[T](...)orclass C[T]:write generic code (Python 3.12+). - Python 3.14 evaluates annotations lazily, so classes can refer to themselves in hints without quotes.
Exercise
Add complete type hints to this function and test it: def average_by_city(students) receives a list of (name, city, marks) tuples, and returns a dictionary from city name to average marks rounded to 1 place. Create a type alias Student for the tuple type. Then call it with four students from Pune and Lucknow and print the result.
Show solution
type Student = tuple[str, str, int]
def average_by_city(students: list[Student]) -> dict[str, float]:
totals: dict[str, list[int]] = {}
for _name, city, marks in students:
totals.setdefault(city, []).append(marks)
return {city: round(sum(m) / len(m), 1) for city, m in totals.items()}
data: list[Student] = [
("Aarav", "Pune", 81),
("Isha", "Lucknow", 92),
("Rohan", "Pune", 74),
("Farhan", "Lucknow", 69),
]
print(average_by_city(data)){'Pune': 77.5, 'Lucknow': 80.5}The alias Student names the tuple shape once and makes the signature easy to read. The empty dictionary totals is annotated because a checker cannot guess its types from {} alone.
Quick Quiz
1. What happens when you call def f(x: int) with a string, without a type checker?
Python stores hints but does not check them when running; tools like mypy do the checking.
2. Which hint means "a list of integers"?
Built-in collection types take their item types in square brackets.
3. How do you write "a string, or None"?
The | operator joins types into a union; Optional[str] is the older spelling.
4. What does def first[T](items: list[T]) -> T: describe?
T is a type variable: whatever item type the list has, the result has the same type.
Learn Python with a mentor
Free lessons get you started. Our Python Programming training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
