Python Type Hints Complete Guide: Write Self-Documenting, Bug-Free Code

Python Type Hints Complete Guide: Write Self-Documenting, Bug-Free Code

Python type hints transform runtime surprises into editor-time catches. A function that returns Optional[User] forces callers to handle the None case before their code even runs. Combined with mypy in strict mode, type hints give Python the safety guarantees of statically typed languages without losing Python’s flexibility.

TL;DR: Annotate all function parameters and return types. Use Optional[T] for nullable, Union[A,B] for multiple types, List/Dict/Tuple from typing or built-in generics (Python 3.9+). Protocol for structural typing. TypedDict for typed dicts. Run mypy –strict in CI.

Basic annotations

from typing import Optional

# Python 3.9+ built-in generics (preferred)
def get_user(user_id: int) -> dict[str, str] | None:
    pass

# Python 3.8 compatible
from typing import Dict, List, Optional, Tuple
def process(items: List[str], config: Dict[str, int]) -> Optional[str]:
    pass

# Function types
from typing import Callable
def apply(fn: Callable[[int, int], int], a: int, b: int) -> int:
    return fn(a, b)

Optional, Union, and Literal

from typing import Optional, Union, Literal

# Optional[T] == T | None
def find_user(email: str) -> Optional[dict]:  # Can return None
    pass

# Union: multiple types
def parse(value: Union[str, int, bytes]) -> str:
    if isinstance(value, bytes):
        return value.decode()
    return str(value)

# Literal: specific values only
Status = Literal['pending', 'active', 'inactive']
def set_status(user_id: int, status: Status) -> None:
    pass
# set_status(1, 'deleted')  # mypy error: not a valid Status

TypedDict and Protocol

from typing import TypedDict, Protocol, runtime_checkable

# TypedDict: typed dictionary structure
class UserRecord(TypedDict):
    id: int
    name: str
    email: str
    is_active: bool

def get_user(user_id: int) -> UserRecord:  # Returns must match structure
    return {'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'is_active': True}

# Protocol: structural typing (duck typing with types)
@runtime_checkable
class Serializable(Protocol):
    def to_dict(self) -> dict: ...
    def to_json(self) -> str: ...

# Any class with these methods satisfies Serializable — no inheritance needed
class User:
    def to_dict(self): return {'id': self.id}
    def to_json(self): return json.dumps(self.to_dict())

def serialize(obj: Serializable) -> str: return obj.to_json()

mypy strict mode setup

# mypy.ini or pyproject.toml
[mypy]
strict = true
# Enables: --disallow-untyped-defs, --disallow-any-generics,
#          --no-implicit-optional, --warn-return-any, and more

# Run in CI:
mypy src/ --strict

# Common fixes for strict mode:
# 1. Add return type to every function
# 2. Replace Dict with dict[str, Any]
# 3. Handle Optional returns before using value
# 4. Add TypeVar for generic functions
  • ✅ Annotate all function params and return types — no bare dict or list
  • ✅ Use Optional[T] (or T | None in 3.10+) for nullable values
  • ✅ TypedDict for structured dicts instead of Dict[str, Any]
  • ✅ Protocol for duck-typed interfaces without inheritance
  • ✅ Run mypy –strict in CI — type errors as build failures
  • ❌ Never use Any as a shortcut — defeats the purpose
  • ❌ Never annotate only some functions — partial typing is misleading

Type hints connect to Pydantic models — Pydantic uses type hints at runtime for validation. External reference: mypy documentation.

Recommended Reading

Designing Data-Intensive Applications — The essential book every senior developer needs.

The Pragmatic Programmer — Timeless engineering wisdom for writing better code.

Affiliate links. We earn a small commission at no extra cost to you.

Free Weekly Newsletter

🚀 Don’t Miss the Next Cheat Code

Join 1,000+ senior developers getting expert JS, Python, AWS and system design secrets weekly.

✓ No spam✓ Unsubscribe anytime

Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply