Skip to main content

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?

BenefitWhy It Matters
Self-documentingThe code tells you what it expects — no need for extra comments.
IDE supportAutocomplete, inline errors, and refactoring tools work better.
Catch bugs earlymypy finds type mismatches before you run the code.
Team collaborationOther 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:

ToolPurpose
mypyStandalone type checker. Run mypy my_file.py to find issues.
IDE (VS Code, PyCharm)Real-time type checking as you type.
pyright / pyreAlternative 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

StepActionOutput
1mypy order_system.pySuccess: no issues found
2Change discount_code: Optional[str] to discount_code: strSuccess: no issues found
3Call create_order("abc", my_cart) with str instead of interror: 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 Any sparingly. It defeats the purpose of type hints.
  • Run mypy in your CI pipeline to catch type errors automatically.
  • Python 3.10+ supports X | Y syntax — no need to import Union.

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 mypy for static analysis and by IDEs for better autocomplete and error detection."


← Back: Functions | Next: Async/Await →