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 StatusTypedDict 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.
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.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- REST API Design Best Practices: The Patterns That Make APIs a Joy to Use
Newly added
- AWS Systems Manager Parameter Store: Hierarchical Runtime Config for Coding Agents
- Amazon Cognito Identity Pools: Federate Coding-Agent Tool Callers Without Long-Lived Keys
- Amazon Athena for Coding Agents: Query Tool Audit Logs and Eval Traces on S3 Without a Warehouse
- AWS Batch for Coding Agents: Parallel Overnight PR Reviews and Long-Running Jobs Without Lambda Timeouts
- Amazon Bedrock Prompt Management: Version and A/B Test Coding-Agent System Prompts
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.
