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

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

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 — Python type hints

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

Related: TypeScript Generics Deep Dive: Write Reusable Code With Full Type Safety · Vibe Coding: How Senior Developers Actually Use AI to Write Code Faster · Python Performance Secrets: 20 Techniques to Make Your Code 10x Faster

Related: SOLID Principles Explained: Write Code That Does Not Break When Requirements Change · Python Decorators Explained: From Simple Wrappers to Production Patterns · Python asyncio vs Threading vs Multiprocessing: When to Use Each

Watch: 100 Days of AI on YouTube

Short videos from CheatCoders, one AI engineering topic per day.

Open the playlist on YouTube · CheatCoders on YouTube

Deep-dive PDF

Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.

Already subscribed? or open the subscribe page.


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 comment

No account needed. Name and email are optional.