How do type hints and mypy/pyright improve code quality, and what are their limitations?

7 minintermediatetypingmypypyrighttype-hints

Quick Answer

Type hints (`def f(x: int) -> str:`) document expected types and let static type checkers (`mypy`, `pyright`) catch type mismatches, missing arguments, and `None`-related bugs **before running the code**, without any runtime cost since hints are (mostly) ignored at runtime. The main limitation: type hints are **not enforced at runtime** by default (Python doesn't stop you from calling `f('wrong type')`), so they only catch what the type checker sees — dynamically constructed calls, `Any`-typed values, and unchecked third-party code can silently bypass them.

Detailed Answer

What type hints look like, and what they don't do at runtime

def greet(name: str) -> str:
    return f"hello, {name}"

greet(42)   # runs FINE at runtime -- Python doesn't check the hint!
            # f"hello, {42}" -> 'hello, 42' -- no error, just probably not intended

Type hints are not enforced by the Python interpreter itself — they're metadata, stored on the function (greet.__annotations__), that tools can optionally read and check. Calling greet(42) doesn't raise TypeError on its own; catching this mismatch requires running a static type checker separately.

Catching errors before running the code

def get_user(user_id: int) -> dict | None:
    ...

user = get_user("123")     # mypy: error: Argument 1 has incompatible type "str"; expected "int"

user = get_user(123)
print(user["name"])          # mypy: error: Item "None" of "dict | None" has no attribute "__getitem__"
                               # (get_user's return type says it might be None!)

mypy/pyright statically analyze the code (no execution needed) and flag both a wrong-type argument and a missed-None-check — the second example is a genuinely common real-world bug class (forgetting a function can return None) that static typing surfaces at review/CI time instead of as a production AttributeError.

Real benefits beyond bug-catching

  • IDE autocomplete/navigation improves dramatically — the editor knows a variable's type and can suggest its actual methods.
  • Self-documenting signaturesdef process(items: list[Order]) -> Summary: communicates intent far better than an untyped signature plus a docstring that can drift out of sync.
  • Safer refactoring — renaming a field or changing a function's signature immediately surfaces every call site the type checker disagrees with.

The limitations

def process(data: Any) -> Any:      # Any opts OUT of checking entirely
    return data.whatever_method()    # never flagged, regardless of what `data` actually is

import third_party_untyped_lib       # if it ships no type stubs, calls into it are unchecked
result = third_party_untyped_lib.do_thing()   # typed as Any by default
  • Any disables checking for anything it touches — a common escape hatch that, if overused, silently reduces how much of the codebase is actually protected.
  • Untyped third-party code (no type stubs, no py.typed marker) is treated as Any by default, creating blind spots at every boundary with such a library.
  • No runtime enforcement — a caller that ignores type errors (or code paths the type checker can't see, like getattr-based dynamic dispatch, or unchecked deserialized JSON) can still pass the wrong type through at runtime; for that, use runtime validation libraries (pydantic) at actual system boundaries.
  • Gradual, not all-or-nothing — a codebase can be partially typed, which is often the pragmatic starting point, but means coverage (and therefore protection) varies file by file until fully adopted.

Interview-ready summary: Type hints let mypy/pyright catch type mismatches and missed-None bugs statically, before running the code, with zero runtime cost and better IDE support as a side benefit — but they're not enforced at runtime, so Any, untyped dependencies, and unchecked dynamic code remain blind spots; use runtime validation (pydantic) at actual data-entry boundaries where static checking alone isn't sufficient.