Type Hinting in Python
You can create Python functions with expected types for arguments and returned values using Type Hinting (introduced in Python 3.5 and enhanced in later versions).
Get Adhi Setyatuhu’s stories in your inbox
Join Medium for free to get updates from this writer.
Type hinting does not enforce types at runtime (Python remains dynamically typed), but it provides several critical benefits for code quality and maintainability:
- Readability: It makes the function’s contract immediately clear to other developers.
- Tooling & Refactoring: It enables powerful static type checkers (like Mypy, Pyright) and IDEs (like VS Code, PyCharm) to catch type-related bugs before the code is run.
- Documentation: It serves as a concise, live form of documentation.
Here is how you implement it, using the standard syntax and key modules.
1. Basic Syntax
The syntax uses a colon (:) after the argument name to indicate its type, and an arrow (->) before the final colon of the function signature to indicate the return type.
def add_numbers(a: int, b: int) -> int:
"""
A simple function with explicit type hints.
Expects two integers and returns one integer.
"""
return a + b# Usage:
result = add_numbers(5, 3) # result is 8 (int)
# If you passed a float, the type checker would flag it:
# result_bad = add_numbers(5.0, 3)2. Using the typing Module for Complex Types
For types that aren’t simple built-ins (like int, str, list), you need to import specific objects from the built-in typing module.
A. Collections (Lists, Dictionaries, Tuples, Sets)
You need to specify the type of the elements inside the collection using square brackets.
from typing import List, Dict, Tuple, Setdef process_data(user_list: List[str], config: Dict[str, bool]) -> Set[str]:
"""
Expects a list containing strings, and a dictionary
where keys are strings and values are booleans.
Returns a set containing strings.
"""
processed_names = {name.upper() for name in user_list}
if config.get('sort_output'):
# Note: Sets can't be sorted directly, but this illustrates the type contract.
return processed_names
return processed_names
# Example with specific type structure:
def get_coordinates() -> Tuple[int, int, int]:
return (10, 20, 30)B. Optional and Union Types
When an argument or return value can be one of several types, or if it can be None, use Union or Optional.
from typing import Optional, Union# Python < 3.10 syntax
def find_item(data: List[str], name: str) -> Optional[str]:
"""Returns a string if found, otherwise returns None."""
if name in data:
return name
return None
# Python 3.10+ syntax (more concise)
def handle_input(value: int | str) -> Union[int, float]:
"""Accepts either an int or a string, returns an int or a float."""
if isinstance(value, int):
return value + 1
return len(value) / 2.0C. Callable (Functions as Arguments)
Use Callable to specify a function that is expected as an argument.
from typing import Callable, Any# Callable[[Arg1_Type, Arg2_Type, ...], Return_Type]
def apply_function(data: List[int], func: Callable[[int], int]) -> List[int]:
"""
Takes a list of integers and a function that accepts an int and returns an int.
"""
return [func(x) for x in data]
def square(x: int) -> int:
return x * x
# Usage:
result = apply_function([1, 2, 3], square) # result is [1, 4, 9]By the way, if you’re into AI agents, I’ve been actively exploring Agentic AI over on my other blog, https://adhisetyatuhu.my.id. Feel free to check it out.







