Lesson 26 • Advanced
Type Hints & Static Typing with MyPy
Modern Python development increasingly depends on static typing — not to replace Python's dynamic nature, but to catch bugs earlier, write clearer APIs, and scale large codebases safely. If you understand type hints deeply, your Python code becomes easier to refactor, safer to modify, more readable, better documented, and more compatible with modern IDE autocompletion.
What You'll Learn in This Lesson
- • Why static typing matters and how it prevents bugs before runtime
- • Annotating function parameters, return values, and variables
- • Using
Optional,Union,TypeVar, andGenerictypes - • Typing collections:
list[str],dict[str, int],tuple, and more - • Running
mypyto catch type errors statically before deployment - • Real-world patterns used in large Python codebases and open-source libraries
🔥 1. Why Use Static Typing in Python?
Without typing:
- Many bugs appear only at runtime
- Refactoring becomes risky
- IDEs can't infer return types
- Large codebases become messy
With typing:
Basic Type Hints
def add(a: int, b: int) -> int:
return a + bYou immediately get:
- Better editor suggestions
- Catching mismatched types early
- Clearer intent
- Fewer runtime surprises
Typing is optional but extremely powerful.
⚙️ 2. Basic Type Hints
Variables
Variable Type Hints
age: int = 18
name: str = "Alice"
balance: float = 99.5Functions
Function Type Hints
def greet(name: str) -> str:
return f"Hello {name}"Multiple parameter types
Multiple Parameter Types
def move(x: float, y: float) -> tuple[float, float]:
return x, yTyping forces clarity.
🧠 3. Optional & Union Types
Sometimes a variable may hold more than one type.
Optional (maybe None)
Optional Types
from typing import Optional
def get_user(id: int) -> Optional[str]:
return "Alice" if id == 1 else NoneUnion (multiple types)
Union Types
from typing import Union
def parse(value: Union[str, int]) -> str:
return str(value)
# Python 3.10+ supports a cleaner syntax:
def parse(value: str | int) -> str:
return str(value)🎛 4. Lists, Dicts & Complex Structures
Complex Type Structures
users: list[str] = ["Alice", "Bob"]
scores: dict[str, int] = {
"math": 95,
"science": 88
}
matrix: list[list[int]] = [
[1, 2, 3],
[4, 5, 6]
]Typed collections prevent silent mistakes.
🔄 5. Callable Types (Typing Functions)
Callable Types
from typing import Callable
def operate(x: int, y: int, fn: Callable[[int, int], int]) -> int:
return fn(x, y)Useful for:
- callbacks
- strategy patterns
- higher-order functions
🧩 6. Typed Classes & Instance Attributes
Typed Classes
class User:
name: str
age: int
def __init__(self, name: str, age: int):
self.name = name
self.age = age
# Typed methods
def birthday(self) -> None:
self.age += 1MyPy ensures you don't assign incorrect types.
📦 7. Dataclasses with Type Hints
Dataclasses with Type Hints
from dataclasses import dataclass
@dataclass
class Product:
id: int
price: float
name: strThis creates:
- typed attributes
- automatic init
- readable models
Perfect for APIs & ML pipelines.
🧬 8. Generics — Creating Reusable Typed Patterns
Generics let you write functions/classes that operate on any type safely.
Generics
from typing import TypeVar, Generic
T = TypeVar("T")
class Box(Generic[T]):
def __init__(self, value: T):
self.value = value
# Use:
b1 = Box(10)
b2 = Box[str]("hello")Powerful for frameworks and libraries.
🎚 9. Protocols (Duck Typing for Static Typing)
A Protocol describes behavior, not inheritance.
Protocols
from typing import Protocol
class Flyer(Protocol):
def fly(self) -> None: ...
class Bird:
def fly(self): print("bird flies")
class Plane:
def fly(self): print("plane flies")
def lift_off(obj: Flyer):
obj.fly()Any object with .fly() works — no inheritance required.
This is extremely powerful.
🧵 10. Literal Types (Exact Allowed Values)
Literal Types
from typing import Literal
def move(direction: Literal["up", "down"]) -> None:
print(direction)If you pass "left", MyPy errors.
Great for:
- API modes
- specific command values
- config flags
🧠 11. Typing None, Never, NoReturn
Function returns no value:
None Return Type
def log(msg: str) -> None:
print(msg)Function never returns (like raise)
NoReturn Type
from typing import NoReturn
def fail(msg: str) -> NoReturn:
raise RuntimeError(msg)Advanced typing helps verify control flow.
🔍 12. Introducing MyPy (Static Type Checker)
Install:
Install MyPy
pip install mypyRun:
Run MyPy
mypy your_file.pyMyPy reads your code + type hints and reports mismatches:
error: Incompatible return value type
It's like a spell-checker for your Python types.
🚀 13. MyPy Configuration (Recommended)
Create mypy.ini:
MyPy Configuration
[mypy]
strict = True
warn_unused_ignores = True
warn_return_any = True
disallow_untyped_defs = True
disallow_untyped_calls = True"Strict mode" catches bugs EARLY.
✨ 14. MyPy + VSCode = Elite Developer Experience
- instant red underlines
- hover type previews
- autocomplete becomes smarter
- safer refactoring
Typing + MyPy = faster development + fewer mistakes.
🔥 15. Enums — Strict Typed Constants
Instead of using strings everywhere, typed enums give you:
- ✔ autocomplete
- ✔ compile-time validation
- ✔ guaranteed allowed values
Typed Enums
from enum import Enum
class Status(Enum):
SUCCESS = "success"
FAIL = "fail"
PENDING = "pending"
def handle(status: Status) -> None:
print("Status:", status.value)If you pass a string, MyPy shows an error.
🧱 16. TypedDict — Type Hints for Dictionaries
Perfect for JSON, API requests, and configuration dictionaries.
TypedDict
from typing import TypedDict
class UserData(TypedDict):
id: int
name: str
verified: bool
user: UserData = {
"id": 1,
"name": "Boopie",
"verified": True
}MyPy catches missing or extra fields.
This is extremely useful for web apps & APIs.
🧪 17. Narrowing Types with isinstance()
MyPy is smart enough to refine types automatically.
Type Narrowing
from typing import Union
def process(x: Union[int, str]):
if isinstance(x, int):
return x + 10 # MyPy knows x is int here
else:
return x.upper() # MyPy knows x is str hereThis makes your branching logic safer and more readable.
🧠 18. Type Aliases — Naming Complex Types
Great for readability:
Type Aliases
Coordinates = tuple[float, float]
JsonDict = dict[str, "JSON"]
def move(point: Coordinates) -> None:
...Aliases make large systems more understandable and maintainable.
🧬 19. Typed Exceptions
You can annotate exceptions to improve clarity and debugging.
Typed Exceptions
def load_user() -> dict | None:
raise FileNotFoundError("User not found")MyPy understands that this function raises, affecting control flow analysis.
🧵 20. Typing Coroutines & Async Functions
Async functions also have return types:
Async Function Types
async def fetch(url: str) -> dict:
...
from typing import Awaitable
def queue_task(task: Awaitable[int]):
...This is important for:
- asyncio pipelines
- queue workers
- async APIs
📡 21. Typing Generator Functions
Generators require three type parameters:
Generator Types
from typing import Generator
def countdown(n: int) -> Generator[int, None, None]:
while n > 0:
yield n
n -= 1Format: Generator[yield_type, send_type, return_type]
This helps for:
- ✔ streaming pipelines
- ✔ async frameworks
- ✔ ML dataset loaders
⚙️ 22. Type-Safe Context Managers
Context Manager Types
from typing import ContextManager
def open_file() -> ContextManager[str]:
return open("data.txt")This powers:
- resource managers
- databases
- thread locks
- file systems
🧩 23. Overloading (Different Types, Same Function Name)
Sometimes the same function behaves differently depending on input type.
Function Overloading
from typing import overload
@overload
def parse(data: int) -> str: ...
@overload
def parse(data: str) -> int: ...
def parse(data):
if isinstance(data, int):
return str(data)
return int(data)MyPy resolves the correct signature.
This technique is common in libraries like NumPy & Pandas.
📦 24. ReadOnly & Final Types
Final variables:
Final Types
from typing import Final
API_KEY: Final = "XYZ123"Final classes & methods prevent inheritance or overriding.
Useful for:
- ✔ security
- ✔ architecture control
- ✔ framework development
⚡ 25. Typed Properties in Classes
Typed Properties
class User:
@property
def name(self) -> str:
return self._nameMyPy verifies property types during assignment.
🔒 26. Private Attributes with Type Checking
Python doesn't enforce private attributes, but typing improves clarity:
Private Attributes
class User:
_token: str # internal useTeams use this to coordinate internal vs public API boundaries.
🧬 27. Structural Subtyping — Protocols in Action
A deeper Protocol + practical use case:
Protocol Use Case
class Database(Protocol):
def connect(self) -> None: ...
def query(self, q: str) -> list[str]: ...Any object with these methods works.
This creates:
- ✔ plug-in architectures
- ✔ dependency injection
- ✔ mockable systems
- ✔ backend abstraction layers
This is how large scalable apps are built.
🧠 28. Using cast() for Type Fixes
Sometimes developers know more than MyPy.
Type Casting
from typing import cast
value = cast(str, some_unknown_value)Use sparingly — only when you are 100% certain.
📊 29. mypy --strict (Real-World, Enterprise Settings)
Strict mode enables:
- ✔ no implicit Optional
- ✔ disallow untyped defs
- ✔ typed bool checks
- ✔ correct narrowing
- ✔ strict Any usage
- ✔ missing return warnings
This avoids massive classes of bugs.
Companies like Meta, Microsoft, Dropbox, and Stripe all use strict typing in their large Python systems.
🔥 30. Full Production Example — Typed API Layer
Typed API Layer
from typing import TypedDict, Optional
class User(TypedDict):
id: int
name: str
is_active: bool
def get_user(user_id: int) -> Optional[User]:
...This creates:
- ✔ safe API responses
- ✔ correct JSON schemas
- ✔ strong autocomplete
- ✔ enforced return types
Typing + MyPy is the foundation of fast, safe backend development.
🔥 31. Typed Dependency Injection (FastAPI / Clean Architecture)
The Clean Architecture pattern separates controllers, services, repositories, and models.
You can use Protocols and TypedDicts for clean interfaces.
Dependency Injection Protocol
class UserRepo(Protocol):
def get(self, user_id: int) -> dict: ...
def save(self, user: dict) -> None: ...Any storage implementation becomes valid:
- ✔ PostgreSQL
- ✔ MongoDB
- ✔ In-memory testing repo
- ✔ Redis
This creates strict boundaries and eliminates class-coupling bugs.
⚙️ 32. Typed Service Layer (Production API Design)
Typed Service Layer
class UserModel(TypedDict):
id: int
email: str
verified: bool
class UserService:
def __init__(self, repo: UserRepo):
self.repo = repo
def verify_user(self, user_id: int) -> UserModel:
user = self.repo.get(user_id)
user["verified"] = True
self.repo.save(user)
return userStatic typing catches:
- ❌ Unknown fields
- ❌ Wrong data shapes
- ❌ Incorrect types
- ✔ Real bugs before runtime
This is how Stripe, Dropbox, and Instagram design services.
🧠 33. Using Typed Exceptions in Architecture
Typing exceptions clarifies control flow:
Typed Exceptions in Architecture
class NotFoundError(Exception):
pass
def get_user(user_id: int) -> dict:
raise NotFoundError()MyPy can detect unreachable code or missing exception handling.
Used in: payment pipelines, async workers, SQL/ORM layers, microservices.
🕸️ 34. Using Typed Generators in Data Pipelines
For ML or ETL:
Typed Generators in Data Pipelines
from typing import Generator
def batch_loader(data: list[int], size: int) -> Generator[list[int], None, None]:
for i in range(0, len(data), size):
yield data[i:i+size]Large frameworks like TensorFlow, Airflow, Spark rely on typed data pipelines like this.
🧩 35. Typed Async Systems (Real World)
Async APIs & concurrency depend heavily on precise typing.
Typed Async Systems
from typing import AsyncIterator
async def stream_events() -> AsyncIterator[str]:
for i in range(100):
yield f"event-{i}"Used in:
- ✔ live dashboards
- ✔ websocket feeds
- ✔ streaming ingestion
- ✔ server push notifications
🔐 36. Protocol-Based Plugin Systems (Extremely Powerful)
With Protocols, you can build interfaces without inheritance:
Protocol-Based Plugin System
class Plugin(Protocol):
name: str
def run(self, data: bytes) -> bytes: ...Any module implementing the methods becomes a valid plugin.
This technique powers: VSCode extensions, Flask extensions, Django middleware, OBS plugins, game modding systems.
Typed, modular, scalable.
📦 37. Interface Segregation with Protocols
Large apps break systems into narrow interface blocks.
Interface Segregation
class ReadOnlyRepo(Protocol):
def get(self, id: int) -> dict: ...
class WriteRepo(Protocol):
def save(self, data: dict) -> None: ...This is how enterprise systems prevent complexity explosions.
🏗️ 38. Declarative API Design with TypedDict + Literal Types
Declarative API Design
from typing import Literal, TypedDict
class Task(TypedDict):
id: int
status: Literal["pending", "running", "done"]Used in:
- ✔ task queues
- ✔ workflow engines
- ✔ job schedulers
- ✔ distributed workers
The system stays consistent and bug-free.
📊 39. Full Real-World Example — Typed Microservice
Service Input/Output schemas:
Typed Microservice
class CreateUserRequest(TypedDict):
email: str
password: str
class CreateUserResponse(TypedDict):
id: int
email: str
verified: bool
def create_user(data: CreateUserRequest) -> CreateUserResponse:
return {
"id": 100,
"email": data["email"],
"verified": False,
}Now your API:
- ✔ rejects invalid payloads
- ✔ catches missing fields
- ✔ auto-documents itself
- ✔ integrates with OpenAPI perfectly
🧪 40. Testing with Typed Mocks
Typing plays a huge role in large codebases for tests.
Typed Mocks
class FakeRepo(UserRepo):
def __init__(self):
self.saved = {}
def get(self, id: int):
return {"id": id, "verified": False}
def save(self, data: dict):
self.saved[data["id"]] = dataMyPy verifies FakeRepo fully matches the protocol.
🧱 41. MyPy in CI/CD Pipelines
Professional workflow:
- Developer pushes code
- Unit tests run
- MyPy runs in strict mode
- Type failures = ❌ build fails
- Only correct, type-safe code reaches production
This is used by: Google, Meta, Uber, Airbnb, Microsoft.
Typing becomes a security layer against bugs.
🧨 42. MyPy + Pydantic = The Ultimate Typed Backend
For APIs, Pydantic validates FastAPI models using the types.
Pydantic Models
from pydantic import BaseModel
class User(BaseModel):
id: int
email: str
verified: boolMyPy ensures correctness.
Pydantic enforces correctness at runtime.
This is a professional-grade combination.
⚡ 43. When Type Hints Improve PERFORMANCE
Type hints inform:
- ✔ JIT compilers
- ✔ static analyzers
- ✔ tooling optimizers
- ✔ IDE optimizations
Python 3.13+ will introduce more optimisations because of typing.
Typed code = faster code.
🎯 44. When to Avoid Type Hints
Don't use typing when:
- ✗ writing throwaway scripts
- ✗ prototyping very early
- ✗ working with extremely dynamic structures
- ✗ you don't know the structure yet
Typing is best for:
- ✔ long-term projects
- ✔ large teams
- ✔ performance-sensitive code
- ✔ shared libraries
🎓 Final Summary — Mastering Python Typing
You now understand everything:
Basic Typing
- ✔ ints, str, bool
- ✔ functions
- ✔ Optional, Union
- ✔ collections
Intermediate Typing
- ✔ dataclasses
- ✔ TypedDict
- ✔ Protocols
- ✔ async & generator typing
- ✔ Callable types
Advanced Patterns
- ✔ overloading
- ✔ generics
- ✔ context manager typing
- ✔ Literal types
- ✔ Final, NoReturn, Never
- ✔ interface segregation
- ✔ dependency injection
Enterprise Usage
- ✔ Backends
- ✔ ML pipelines
- ✔ microservices
- ✔ async architectures
- ✔ plugin systems
- ✔ ETL pipelines
Tooling
- ✔ MyPy strict
- ✔ VSCode integration
- ✔ CI/CD workflows
You now write code with:
🔥 fewer bugs
🔥 better structure
🔥 enterprise-level quality
🔥 professional readability
🔥 future-proof maintainability
Typing + MyPy = elite Python engineering.
📋 Quick Reference — Type Hints
| Syntax | What it does |
|---|---|
| def fn(x: int) -> str: | Annotate function params and return |
| Optional[str] | Value can be str or None |
| Union[int, str] | Value can be int or str |
| list[str] / dict[str, int] | Generic container types (Python 3.9+) |
| TypeVar('T') | Generic type variable for templates |
🎉 Great work! You've completed this lesson.
You can now annotate functions, variables, and generics with type hints and use mypy to catch type errors before runtime.
Up next: Data Classes — use @dataclass to eliminate boilerplate and build clean, self-documenting classes.
Sign up for free to track which lessons you've completed and get learning reminders.