Python Type Hints
Mentor's Note: Type hints are like labels on a filing cabinet — they tell you what's inside without opening the drawer. Your future self and your teammates will thank you! 💡
The Scenario: The Label Maker
Imagine walking into a warehouse where every box is unmarked.
- Without Labels: You have to open every box to find what you need. You make mistakes. You waste time.
- With Labels: "Nails 🔩", "Bolts 🔧", "Screws 🪛". You know exactly what's in each box instantly.
- The Result: Faster work, fewer mistakes, happier team. ✅
Concept Explanation
1. What Are Type Hints?
Type hints are optional annotations that tell you (and tools) what type a variable should be or what a function expects/returns. Python ignores them at runtime — they are for humans and linters.
# Without type hints
def add(a, b):
return a + b
# With type hints
def add(a: int, b: int) -> int:
return a + b
2. Why Use Them?
| Benefit | Why It Matters |
|---|---|
| Self-documenting | The code tells you what it expects — no need for extra comments. |
| IDE support | Autocomplete, inline errors, and refactoring tools work better. |
| Catch bugs early | mypy finds type mismatches before you run the code. |
| Team collaboration | Other developers know exactly what your functions expect. |
Basic Syntax
Variable Annotations
name: str = "Alice"
age: int = 30
height: float = 5.8
is_active: bool = True
nothing: None = None
Function Annotations
def greet(name: str) -> str:
return f"Hello, {name}!"
def divide(a: int, b: int) -> float:
return a / b
def log_message(message: str) -> None:
print(f"[LOG]: {message}")
Collection Types
Import from the typing module for collection type hints:
from typing import List, Dict, Tuple, Set
# List of integers
scores: List[int] = [95, 87, 91]
# Dictionary mapping strings to integers
ages: Dict[str, int] = {"Alice": 30, "Bob": 25}
# Tuple with fixed types
coordinate: Tuple[float, float] = (40.7128, -74.0060)
# Set of strings
tags: Set[str] = {"python", "hints", "typing"}
Optional and Union
When a value could be None or one of several types:
from typing import Optional, Union
# Optional[str] means str or None
def find_user(user_id: int) -> Optional[str]:
if user_id == 1:
return "Alice"
return None # User not found
# Union[int, str] means int or str
def parse_value(value: Union[int, str]) -> str:
return str(value)
In Python 3.10+, you can use X | Y instead of Union[X, Y] and X | None instead of Optional[X]:
# Python 3.10+ syntax
def find_user(user_id: int) -> str | None: ...
def parse_value(value: int | str) -> str: ...
Type Aliases
Give complex types a readable name:
from typing import List, Tuple
# Type alias
Coordinate = Tuple[float, float]
Route = List[Coordinate]
# Now use the alias
def calculate_distance(route: Route) -> float:
...
def add_point(route: Route, point: Coordinate) -> Route:
return route + [point]
Visual Logic: Type Hierarchy
Implementation: Before and After
Before (No Type Hints)
def process_data(data, multiplier):
result = []
for item in data:
result.append(item * multiplier)
return result
def get_user_info(user_id):
users = {1: "Alice", 2: "Bob"}
return users.get(user_id)
# What types are data, multiplier, and the return value?
# You have to read the code to figure it out.
After (With Type Hints)
from typing import List, Optional
def process_data(data: List[float], multiplier: float) -> List[float]:
result: List[float] = []
for item in data:
result.append(item * multiplier)
return result
def get_user_info(user_id: int) -> Optional[str]:
users: dict[int, str] = {1: "Alice", 2: "Bob"}
return users.get(user_id)
# Everything is clear from the signature alone.
Practical Example: E-commerce Order System
from typing import List, Optional, Dict
from datetime import datetime
# Type aliases
ProductID = int
Quantity = int
# Data structures
Cart = Dict[ProductID, Quantity]
def create_order(
user_id: int,
cart: Cart,
discount_code: Optional[str] = None
) -> Dict[str, object]:
"""Create an order from a user's cart."""
order = {
"user_id": user_id,
"items": list(cart.items()),
"total": sum(cart.values()) * 9.99,
"discount": discount_code,
"created_at": datetime.now().isoformat()
}
return order
def apply_discount(total: float, code: str) -> float:
if code == "SAVE10":
return total * 0.9
return total
# Type-checked usage
my_cart: Cart = {101: 2, 102: 1}
order = create_order(user_id=42, cart=my_cart, discount_code="SAVE10")
print(order)
When Are Type Hints Checked?
Type hints are not enforced by Python at runtime. They are checked by:
| Tool | Purpose |
|---|---|
| mypy | Standalone type checker. Run mypy my_file.py to find issues. |
| IDE (VS Code, PyCharm) | Real-time type checking as you type. |
| pyright / pyre | Alternative type checkers from Microsoft and Meta. |
# mypy will catch this:
age: int = "twenty-five" # Error: Incompatible types
name: str = 42 # Error: Incompatible types
def get_age() -> int:
return "old" # Error: Incompatible return value
Run mypy on your code:
pip install mypy
mypy my_script.py
Sample Dry Run
File: order_system.py with mypy
| Step | Action | Output |
|---|---|---|
| 1 | mypy order_system.py | Success: no issues found ✅ |
| 2 | Change discount_code: Optional[str] to discount_code: str | Success: no issues found |
| 3 | Call create_order("abc", my_cart) with str instead of int | error: Argument 1 to "create_order" has incompatible type "str"; expected "int" ❌ |
Pro Tips
- Start adding type hints to new code only — don't rewrite existing codebases overnight.
- Use
Anysparingly. It defeats the purpose of type hints. - Run
mypyin your CI pipeline to catch type errors automatically. - Python 3.10+ supports
X | Ysyntax — no need to importUnion.
Interview Tip
"Interviewers often ask: 'Are type hints enforced at runtime?' The answer is no — Python ignores them. They are used by external tools like
mypyfor static analysis and by IDEs for better autocomplete and error detection."