šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

MyPy: Static Type Checking for Python

mypy in practice — running it, reading its output, the --strict mode question, and integrating it into a workflow so the type hints from earlier in this curriculum actually get verified.

⚔ Total XP: 0|šŸ’» python XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Core logic.

Quick Quiz //

Without --strict, what does mypy do with an entirely unannotated function like process(data)?


šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

The Type Hints lesson established that annotations are inert without a checker actually reading them. This lesson is that checker, in practice: running mypy, understanding its output, and the --strict mode decision that determines how much verification you're actually getting.

1What Actually Happens When You Run mypy

The Type Hints lesson established, correctly but somewhat abstractly, that type hints are only meaningful if 'a static checker like mypy' actually reads and verifies them. This lesson makes that concrete: mypy script.py genuinely analyzes the code — tracing how values flow through function calls, assignments, and expressions — without executing any of it, and reports every place where an annotated type doesn't match how a value is actually used. greet(42), where greet is annotated to accept a str, produces a specific, clear error: Argument 1 to "greet" has incompatible type "int"; expected "str" — caught before the code ever runs, let alone reaches production.

This is genuinely different from a runtime error: mypy's analysis happens entirely statically, examining the source code's structure and the declared types, with no actual execution involved — which is precisely why it can catch this specific mismatch even in a code path that might rarely execute in practice (an error-handling branch, an edge case), places where a runtime-only testing strategy might never happen to exercise that exact combination of values.

Running mypy regularly — ideally integrated into your editor for immediate feedback, and definitely as a required CI check (the Pre-commit lesson covers exactly this integration) — turns the type hints this curriculum has written throughout into a genuine, continuously-enforced correctness guarantee, rather than documentation that happens to also be readable by a checker nobody actually runs.

āœ•
—
+
def greet(name: str) -> str:
    return f"Hello, {name}!"

greet(42)  # a plain int, where a str was declared

$ mypy script.py
script.py:3: error: Argument 1 to "greet" has incompatible type "int"; expected "str"
Found 1 error in 1 file (checked 1 source file)
localhost:3000
Static Verification
mypy script.py
error: incompatible type "int"; expected "str" — caught before any code runs

2Default Mode's Deliberate Leniency: Supporting Gradual Adoption

mypy's default (non---strict) behavior is genuinely, deliberately lenient in a way worth understanding precisely, not just knowing about abstractly: an entirely unannotated function, like process(data) with no type hints at all, receives very little checking by default — mypy largely leaves it alone, meaning a genuine type error inside it (calling .upper() on a value that turns out to be an int) goes entirely undetected, the exact runtime AttributeError that type hints exist to catch statically slips through unchecked.

This leniency isn't an oversight; it's a deliberate design choice supporting mypy's core adoption story — the Type Hints lesson's point about *gradual* typing, where annotated and unannotated code coexist freely, requires a checker that doesn't produce an overwhelming wall of errors the moment it's pointed at a large, partially-typed (or entirely untyped) existing codebase. Default mode lets a team introduce mypy incrementally, annotating and gaining verification for one module at a time, without needing to annotate the entire codebase in one disruptive effort just to get mypy running without errors everywhere.

The cost of this leniency, worth being explicit about: default mode's actual verification coverage is only as comprehensive as your codebase's actual annotation coverage. A codebase with type hints on 30% of its functions gets genuine, meaningful checking on that 30%, and essentially none on the remaining 70% — a fact easy to lose sight of if 'we run mypy in CI' is treated as a blanket assurance of type safety without checking what fraction of the codebase that CI check is actually verifying.

āœ•
—
+
# WITHOUT --strict: this function has NO annotations, and mypy says NOTHING about it
def process(data):
    return data.upper()

process(42)  # would crash at runtime -- but mypy, by default, doesn't check this AT ALL
localhost:3000
Deliberate Gradual-Adoption Leniency
Unannotated function
Largely unchecked by default — genuine errors inside it can slip through silently

3--strict: Closing the Gap, Deliberately

mypy --strict (or strict = true under [tool.mypy] in pyproject.toml) removes exactly the leniency covered above: it requires every function to carry type annotations (flagging no-untyped-def for any that don't), and it enables a bundle of additional, individually-stricter checks (disallowing implicit Any types in various contexts, requiring more precise generic type usage, and several other specific rules) that default mode leaves relaxed. The practical effect is closing the gap between 'mypy runs successfully' and 'mypy has genuinely, comprehensively verified this codebase' — under --strict, those two statements become far closer to actually meaning the same thing.

The trade-off, and the reason --strict isn't simply always the obvious right choice from day one: adopting it on an existing, substantially unannotated codebase produces an immediate flood of no-untyped-def errors for every function lacking annotations — a real, potentially disruptive amount of upfront work to reach a clean --strict pass, which is precisely the disruption default mode's leniency was designed to avoid for teams gradually adopting typing.

The practical, common professional pattern: start with default (lenient) mode to introduce mypy without a disruptive big-bang annotation effort, then deliberately, incrementally tighten toward --strict — either enabling it codebase-wide once annotation coverage is genuinely comprehensive, or enabling it selectively per-module (mypy supports per-module strictness configuration) so newly-written or already-fully-annotated modules get full --strict rigor immediately, while older, not-yet-annotated modules are addressed on their own timeline rather than blocking the whole team's CI on day one.

āœ•
—
+
[tool.mypy]
strict = true

$ mypy .
script.py:5: error: Function is missing a type annotation  [no-untyped-def]
# --strict FORCES annotation coverage -- it won't silently skip unannotated code anymore
localhost:3000
Comprehensive Verification
mypy --strict
Closes the gap — 'mypy passes' starts to genuinely mean 'comprehensively verified'

4Step-by-Step Breakdown

Every type hint you've written throughout this curriculum has been verified by exactly one thing so far: your own eyes. mypy is what actually checks them.

mypy reads your type hints and verifies they're used CONSISTENTLY throughout your codebase -- catching mismatches before runtime.

Without --strict, mypy is genuinely LENIENT by default -- unannotated functions are mostly ignored entirely, not flagged as a problem.

Checkpoint: Without --strict, what does mypy do with an entirely unannotated function like process(data)?

  • →It mostly skips checking it entirely -- an unannotated function receives very little verification by default
  • →It flags the missing annotations as an error, the same way --strict would

mypy --strict requires EVERY function to be annotated, and enables the full set of stricter checks -- genuinely comprehensive verification, not just 'checks whatever happens to be annotated'.

Checkpoint: What does mypy --strict fundamentally change about how much of a codebase actually gets verified?

  • →It requires every function to be annotated and enables stricter checks throughout, closing the gaps default mode leaves unchecked
  • →It primarily makes mypy run faster, with a similar level of verification

MyPy verifies types statically; Pre-commit is the next lesson, for making sure mypy (and every other check covered in this section) actually runs before code ever reaches a shared branch.

Detect a Real Type Mismatch. Finish would_mypy_flag(): mypy statically compares the declared type against the actual argument type.

Level Up šŸš€

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported (via server-side Python execution).

FirefoxSupported

Fully supported (via server-side Python execution).

SafariSupported

Fully supported (via server-side Python execution).

EdgeSupported

Fully supported (via server-side Python execution).

Best Practices

Understand exactly how much of your codebase mypy is actually verifying, not just whether it's configured

In default (non-strict) mode, an unannotated function is barely checked at all -- 'mypy runs in CI' is not the same guarantee as 'mypy comprehensively verifies this codebase' unless annotation coverage is genuinely high or --strict is enabled.

Adopt --strict incrementally (per-module, or after achieving broad annotation coverage) rather than avoiding it indefinitely

Default mode's leniency is meant as a gradual-adoption starting point, not a permanent destination -- tightening toward --strict over time closes real gaps default mode leaves unchecked.

Frequent Bugs

THE BUG

Assuming 'mypy passes in CI' means comprehensive type verification, without realizing default (non-strict) mode leaves entirely unannotated functions largely unchecked, letting genuine type errors inside them go undetected indefinitely.

THE FIX

Check actual annotation coverage across the codebase, and adopt --strict (codebase-wide or per-module) to close the gap between 'mypy runs without errors' and 'mypy has genuinely verified this code'.

Real-World Examples

Incrementally Adopting --strict Per-Module

A team wants new code to be held to full --strict standards immediately, while gradually bringing older, not-yet-fully-annotated modules up to the same standard over time.

[tool.mypy]
# Lenient baseline for the whole codebase
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "myapp.new_feature.*"
strict = true  # new code: full strict verification from day one

[[tool.mypy.overrides]]
module = "myapp.legacy.*"
disallow_untyped_defs = false  # legacy code: not yet fully annotated, addressed over time

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Assuming a passing mypy CI check means the entire codebase has been type-verified, when default (non-strict) mode is actually leaving substantial portions of unannotated code essentially unchecked.

# Misleading: passes, but unannotated code is barely checked def process(data): # no annotations at all return data.upper() # mypy default mode: essentially unchecked # Correct: --strict would flag this immediately # error: Function is missing a type annotation [no-untyped-def] def process(data: str) -> str: return data.upper()

The Solution //

Check actual annotation coverage, and enable --strict (codebase-wide or via targeted per-module overrides) to close the gap between "mypy passes" and genuine, comprehensive type verification.

Lesson Glossary

[01]mypy

The most widely-used static type checker for Python, analyzing type-annotated code without executing it.

Code Preview
// mypy context

[02]mypy --strict

A mypy mode requiring full annotation coverage and enabling additional strict checks, closing gaps default mode leaves unverified.

Code Preview
// mypy --strict context

[03]no-untyped-def

A mypy --strict error flagging a function with no type annotations, which default mode does not flag.

Code Preview
// no-untyped-def context

[04]Gradual typing adoption

The practice of incrementally introducing type hints and mypy checking across a codebase, rather than annotating everything at once.

Code Preview
// Gradual typing adoption context

Continue Learning