Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes
How-tos

Add Type Hints to Existing Python Projects

Learn a practical, gradual strategy for adding type hints to an existing Python codebase without breaking your workflow. Start with critical paths, handle optional types, and avoid common mistakes.

July 2026 5 min read 2 views 0 hearts

Adding Type Hints to Your Python Project Without Losing Your Mind

You've got a perfectly working Python project. It runs. It does what it's supposed to do. But as it grows, you start wondering: "Should I add type hints?"

The short answer is yes. But doing it wrong can turn your codebase into a debugging nightmare. Here's how to add Python type hints to an existing project the right way.

Why Bother Adding Type Hints?

Type hints aren't just for show. They catch bugs before they happen. When you come back to code six months later (and you will), type hints tell you exactly what a function expects and returns. No more guessing whether get_user(id) expects an integer or a string.

At PythonSkillset, we've seen type hints reduce debugging time by nearly 30% in existing projects. That's real time you can spend building features instead of scratching your head.

Start with the Critical Paths

Don't try to type-hint everything at once. That's a recipe for burnout.

Instead, start with: - Public API functions - the functions other modules call - Data processing pipelines - where bugs are expensive - Functions with complex return types - where mistakes are common

Here's a practical example. Say you have this function:

def process_order(order_id, items, discount_code):
    total = 0
    for item in items:
        total += item.price
    if discount_code:
        total *= 0.9
    return {"order_id": order_id, "total": total}

The first thing to ask: what types are order_id, items, and discount_code? After checking the code, you might find: - order_id is an integer (database ID) - items is a list of OrderItem objects - discount_code is a string or None

So the typed version becomes:

from typing import Optional

def process_order(order_id: int, items: list[OrderItem], discount_code: Optional[str] = None) -> dict:
    total = 0
    for item in items:
        total += item.price
    if discount_code:
        total *= 0.9
    return {"order_id": order_id, "total": total}

Handle Optional and None Carefully

This is where most people get tripped up. If a parameter can be None, use Optional[type] or type | None (Python 3.10+).

def find_user(user_id: int) -> Optional[User]:
    """Returns User if found, None otherwise."""
    try:
        return database.get_user(user_id)
    except UserNotFound:
        return None

Dealing with Complex Types

Real-world projects have messy types. You'll encounter: - Dictionaries with mixed value types - use dict[str, Any] - Functions returning multiple types - use Union[type1, type2] - Custom classes - just use the class name

from typing import Union, Any

def parse_config(config_path: str) -> dict[str, Any]:
    config: dict[str, Any] = {}
    with open(config_path) as f:
        for line in f:
            key, value = line.strip().split("=")
            config[key] = value
    return config

The Gradual Adoption Strategy

Here's what works at PythonSkillset:

  1. Week 1: Add type hints to new functions only
  2. Week 2: Fix type errors found by mypy on critical paths
  3. Week 3: Add type hints to existing functions when you touch them (the "touching rule")
  4. Week 4: Run mypy in CI, but don't fail the build yet
  5. Week 5: Make mypy errors fail the build

Common Mistakes to Avoid

Don't overuse Any. If you use Any everywhere, you lose the benefit. Be specific when possible.

Don't ignore imports. When you start typing, you'll need imports like from typing import List, Optional, Dict. That's normal.

Don't type-hint third-party libraries. If requests.get() returns something complex, just use Any for those parts.

Tools That Make It Easier

  • mypy: The standard type checker
  • pyright: Microsoft's type checker (used by VS Code)
  • monkeytype: Automatically generates type hints from runtime data
  • typeguard: Runtime type checking for debugging

What About Performance?

Type hints don't affect runtime performance in Python. They're stripped out at compile time. Your code runs exactly the same speed with or without them.

The Bottom Line

Adding type hints to an existing project is like adding labels to a messy toolbox. It takes time upfront, but you'll thank yourself later. Start small, be consistent, and let the tools catch your mistakes.

Your future self—and anyone else who touches your code—will appreciate the clarity.

Comments

Questions, corrections, and tips stay visible for everyone reading this page.

0 in thread

Join the discussion

Shown next to your comment.

Up to 4,000 characters

No comments yet

Be the first to leave a note — it helps the next reader.