Dead code is the tech debt nobody talks about. Unused functions, orphaned imports, abandoned classes — they get maintained, reviewed in PRs, and tested in CI. And they do absolutely nothing.
I wanted to answer two questions:
- How much dead code exists in the most popular Python projects on GitHub?
- Can static analysis tools reliably detect it without drowning you in false positives?
So I ran dead code detection on 9 of the most popular Python repositories (350k+ combined stars) and manually verified every single finding.
The 9 Python Repos I Tested
| Repository | Stars | Why it's a good stress test |
|---|---|---|
| fastapi/fastapi | 82k | 100+ Pydantic model fields for OpenAPI specs |
| pallets/flask | 69k | Jinja2 template globals, Werkzeug protocol methods |
| psf/requests | 53k | Heavy __init__.py re-exports |
| Textualize/rich | 51k |
__rich_console__ protocol, metaclasses |
| tqdm/tqdm | 30k | Keras/Dask callbacks, pandas monkey-patching |
| pydantic/pydantic | 23k | Mypy plugin hooks, __getattr__ dynamic config |
| pallets/click | 17k | IO protocol methods, nonlocal closures |
| encode/httpx | 14k | Transport/auth protocol methods — zero dead code |
| encode/starlette | 10k | ASGI interface params, polymorphic dispatch |
Every finding was manually verified against the source code. No automated labelling. No cherry-picking.
How Much Dead Code Did I Find?
Across all 9 repos: 52 genuinely dead items — unused functions, classes, imports, and variables.
But here's the interesting part: the false positive problem is way worse than the dead code itself.
I compared two Python dead code detection tools — Vulture (the most popular Python dead code finder) and Skylos (a framework-aware tool I built to reduce false positives).
Python Dead Code Detection: Skylos vs Vulture
| Repository | Dead Items | Skylos Found | Skylos FP | Vulture Found | Vulture FP |
|---|---|---|---|---|---|
| psf/requests | 6 | 6 | 35 | 6 | 58 |
| pallets/click | 7 | 7 | 8 | 6 | 6 |
| encode/starlette | 1 | 1 | 4 | 1 | 2 |
| Textualize/rich | 13 | 13 | 14 | 10 | 8 |
| encode/httpx | 0 | 0 | 6 | 0 | 59 |
| pallets/flask | 7 | 7 | 12 | 6 | 260 |
| pydantic/pydantic | 11 | 11 | 93 | 10 | 112 |
| fastapi/fastapi | 6 | 6 | 30 | 4 | 102 |
| tqdm/tqdm | 1 | 0 | 18 | 1 | 37 |
| Total | 52 | 51 | 220 | 44 | 644 |
Summary
| Metric | Skylos | Vulture |
|---|---|---|
| Recall | 98.1% (51/52) | 84.6% (44/52) |
| False Positives | 220 | 644 |
| Dead items found | 51 | 44 |
Skylos finds 7 more dead items with 3x fewer false positives.
Why Python Dead Code Detection Produces So Many False Positives
The biggest source of noise? Python framework magic. Django, Flask, FastAPI, and pytest all use patterns that look like dead code to static analysis but are very much alive at runtime.
Flask: 260 False Positives from Vulture
# Vulture flags this as unused — but Jinja2 calls it at render time
@app.template_global()
def format_date(dt):
return dt.strftime("%Y-%m-%d")
Vulture reported 260 false positives on Flask. Most were Jinja2 template globals and Werkzeug protocol methods that Flask calls internally. Skylos reported 12 because it recognizes Flask-specific patterns.
FastAPI: Pydantic Model Fields Aren't Unused Variables
class ValidationError(BaseModel):
detail: str
status_code: int = 422 # Vulture: "unused variable"
headers: dict | None = None # Vulture: "unused variable"
Pydantic BaseModel fields define your API schema. They're serialized, validated, and documented by OpenAPI — but never "called" in the traditional sense. Vulture flagged 102 of these in FastAPI. Skylos flagged 30.
Where Skylos Still Gets It Wrong (Honestly)
No Python dead code tool is perfect. Some patterns still fool Skylos:
| Repo | Skylos FP | Vulture FP | Why Skylos loses |
|---|---|---|---|
| click | 8 | 6 | IO protocol methods on io.RawIOBase subclasses |
| starlette | 4 | 2 | Instance method calls not resolved to class definitions |
| rich | 14 | 8 | Sentinel vars checked via f_locals.get("name")
|
When code uses very dynamic Python patterns like frame inspection (f_locals), both tools struggle. Vulture actually does better on rich because its more conservative analysis happens to avoid those specific cases.
The Python Repo with Zero Dead Code
httpx had zero dead items. Every function, class, and import is used. It's one of the cleanest Python codebases I've seen.
But Vulture still reported 59 false positives on it — mostly transport and auth protocol methods that implement interfaces without explicit callers in the same codebase. Skylos reported 6.
A tool that reports 59 issues when there are 0 real problems trains developers to ignore its output entirely.
What Dead Code I Actually Found in Popular Python Projects
Some highlights from genuinely dead code:
-
requests: 6 dead items including unused re-exports in
__init__.pythat survived years of refactoring - rich: 13 dead items — unused utility functions and classes that were replaced but never removed
- pydantic: 11 dead items including leftover mypy plugin hooks from API changes
- flask: 7 dead items — old extension hooks that nothing calls anymore
None of these are security vulnerabilities. But they add up: dead code gets reviewed in PRs, confuses new contributors, and creates false dependencies that make refactoring harder.
How to Find Dead Code in Your Own Python Project
All benchmark scripts and ground truth data are open source:
git clone https://github.com/duriantaco/skylos-demo
cd skylos-demo
# Run any individual benchmark
cd real_life_examples/flask
python3 ../benchmark_flask.py
# Or install and try on your own project
pip install skylos
skylos your-project/
Skylos also does security scanning (taint analysis, hardcoded secrets, SQL injection) and has an AI remediation agent that can auto-fix issues and open PRs.
Full methodology, ground truth lists, and per-repo breakdowns: skylos-demo
Key Takeaways
- Dead code exists everywhere — even in the most popular, well-maintained Python projects
- False positives are the real problem — a tool that reports 644 issues when only 52 are real trains you to ignore static analysis entirely
- Framework awareness matters for Python — Django views, FastAPI endpoints, Pydantic fields, pytest fixtures — if your dead code tool doesn't understand Python frameworks, most of its output is noise
- Zero dead code is achievable — httpx proves it. Clean Python codebases exist.
What does your project's dead code situation look like? Try running pip install skylos && skylos . and let me know in the comments.
Top comments (0)