fastapi's routing and encoding layer carries the highest risk — 5 functions to address first

All five top hotspots in tiangolo/fastapi are 'fire'-quadrant functions — structurally complex and touched within the last 16 days — with god-function and complex-branching patterns concentrated in fastapi/routing.py, fastapi/encoders.py, and fastapi/openapi/utils.py.

Stephen Collins ·
Generated by hotspots · free & open source
pip
$ pip install hotspots-cli
Activity Risk18.18Low
Hottest Functionget_request_handler

Antipatterns Detected

god_function5long_function5complex_branching4exit_heavy4deeply_nested3hub_function1

Run this on your own codebase

See if your own repo has a get_request_handler-style hotspot — run this in any local git repo:

pip
$ pip install hotspots-cli
npm
$ npm install -g @stephencollinstech/hotspots
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Key Points

What is a god function and why does it matter in fastapi?

A god function is a single function that has accumulated so many responsibilities that it controls a disproportionate share of the system's behavior — it calls many other functions, contains complex branching logic, and is too long to reason about in one pass. In fastapi, all five top hotspots carry this pattern, and the problem is especially acute for functions like `get_request_handler` and `jsonable_encoder` that sit on critical paths: every request that fastapi handles passes through request handler construction, and every response passes through serialization. A change to a god function has a wide blast radius because so many behaviors depend on its internal logic, and its size makes it hard to predict which of its many code paths a given change will affect. Testing a god function adequately requires covering all of its branches in combination, which quickly produces a test matrix that is impractical to maintain.

How do I reduce cyclomatic complexity in Python?

The most reliable technique is extract-method refactoring: identify a coherent cluster of branches inside the function — a type-check block, a validation sequence, a formatting step — and pull it out into a named function with a clear single responsibility. A cyclomatic complexity above 15 warrants splitting; above 30, the function should be treated as an immediate refactoring target. For `get_openapi_path`, which has a cyclomatic complexity of 77, a concrete first step is to identify the parameter resolution logic as a self-contained unit and extract it into a dedicated function — that single extraction could reduce the parent function's branch count by a substantial fraction while also making the extracted logic independently testable. Python's support for small, composable functions with expressive names makes this kind of decomposition idiomatic and low-friction.

Is fastapi actively maintained?

The data shows clear signs of active development: all five top hotspots are in the 'fire' quadrant, meaning they combine high structural complexity with recent commit activity. Each of the top five functions recorded touches_30d of 1 and days_since_changed of 16, placing them all within the last two and a half weeks. Among the context-only functions, `_frontend_scope_specificity` in `fastapi/routing.py` was modified just 5 days ago, and `render_banner_sponsors` in `scripts/docs.py` 9 days ago. There are zero functions in the debt quadrant across all 1,154 analyzed, which means no structurally complex function has been left entirely dormant. Active development and structural complexity are not mutually exclusive: the high cyclomatic complexity scores in core functions like `get_openapi_path` (CC 77) and `jsonable_encoder` (CC 51) reflect accumulated scope rather than neglect, and the commit activity confirms the team is continuing to iterate on them.

How do I reproduce this analysis?

The hotspots CLI is available at github.com/gethotspots/hotspots. To reproduce this exact analysis, check out commit `3f3354a` of tiangolo/fastapi with `git checkout 3f3354a`, then run `hotspots analyze . --mode snapshot --explain-patterns --force` from the repository root. The same command works on any local git repository without any additional configuration — no `.hotspotsrc.json` is required to get started, though you can add one to exclude paths like `scripts/` if you want to scope the analysis to the framework source only.

What does activity-weighted risk mean?

Activity-weighted risk multiplies a function's structural complexity — derived from its cyclomatic complexity, nesting depth, and fan-out — by how frequently it has been modified in recent commits. The core insight is that a structurally complex function that nobody is touching right now poses lower near-term regression risk than a moderately complex function that is being changed every few days. For example, `get_openapi_path` has a cyclomatic complexity of 77, but if it had not been touched in two years its risk score would be far lower than its current 15.45 — it is the combination of that structural density and its recent commit activity that makes it a live concern. This framing helps engineering teams direct refactoring effort toward functions where the probability of introducing a bug in the next sprint is highest, rather than simply toward the most complicated-looking code in the repository.

Across 1,154 functions analyzed at commit 3f3354a, fastapi has 64 critical-band functions and 220 in the ‘fire’ quadrant — structurally complex and actively changing at the same time. Every function in the top five was modified within the last 16 days, which means the structural risk is not theoretical: it is present in code that is being shipped right now. I would start with get_request_handler in fastapi/routing.py, which carries a risk score of 18.18, flags as a god function with a fan-out of 60, and sits directly on every request path the framework handles.

The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).

Top 5 Hotspots

FunctionFileRiskCCNDFO
get_request_handlerfastapi/routing.py18.217760
jsonable_encoderfastapi/encoders.py17.851424
remove_unused_docs_srcscripts/docs.py17.464521
mainscripts/deploy_docs_status.py15.742336
get_openapi_pathfastapi/openapi/utils.py15.477644

Codemod / Tooling Files in Results

Two of the top five hotspots — remove_unused_docs_src in scripts/docs.py and main in scripts/deploy_docs_status.py — are documentation tooling scripts rather than framework source code. They score highly on structural metrics because they contain dense filesystem traversal and deployment orchestration logic, not because they affect fastapi’s runtime behavior. If you want to focus the analysis exclusively on the framework’s importable surface, you can exclude the scripts directory with the following .hotspotsrc.json entry: { "exclude": ["scripts/"] }. This will remove both tooling hotspots from the ranked output without affecting analysis of fastapi/ itself.

Quadrant Distribution

Triage Band Distribution
Fire220Watch934

1,154 functions analyzed

The quadrant picture is unusually clean: zero functions in debt or ok, and 220 in fire. Every structurally complex function in this repo is also seeing recent activity. That eliminates the usual triage question of ‘is this dormant enough to defer?’ — it is not.

Detected Antipatterns
God Function×5God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.
Long Function×5Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.
Complex Branching×4Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.
Exit Heavy×4Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.
Deeply Nested×3Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.
Hub Function×1Hub Function
Many other functions call this one — a change here ripples widely through callers.

The pattern counts tell a consistent story: all five top hotspots are flagged as both god functions and long functions. Four carry complex branching and exit-heavy patterns. This is not a case of one outlier pulling up the averages — the top of the risk distribution is uniformly dense.


Top 5 Hotspots

get_request_handler — fastapi/routing.py

get_request_handler
fastapi/routing.py
18.18
critical
CC 17
ND 7
FO 60
touches/30d 1

get_request_handler is the function that constructs the actual async handler callable for a route — it is, by name and location, the piece of machinery that wires a path operation function to the ASGI request/response cycle. With a fan-out of 60, it calls into more than five dozen distinct functions from one place. In Python, where duck typing means many of those call targets are resolved at runtime, a fan-out of 60 implies coupling that is wider than the number alone suggests — decorator chains, dependency injection hooks, and background task wiring all likely run through here.

Fan-out 60
threshold: 15

The nesting depth of 7 is a strong refactoring signal on its own. Combined with a cyclomatic complexity of 17 — meaning 17 independent execution paths that each require their own test case — and the deeply_nested and exit_heavy pattern flags, this function is genuinely hard to reason about end-to-end. The exit_heavy flag adds to the test-coverage burden: multiple early-return paths mean a test suite needs significantly more cases to achieve meaningful branch coverage.

The external signals here are worth noting in context: pr_review_comment_density of 3.6 on a file with only 3 total commits suggests reviewers are catching things they need to discuss. There are no bug-linked commits or reverts, which means the historical record is clean — but the structural conditions for subtle regressions are present.

The god_function and long_function patterns together suggest this function has accumulated responsibilities that belong in separate, testable units. My recommendation: extract the dependency resolution logic, the background task wiring, and the response serialization steps into named helper functions. That alone would reduce both the fan-out and the cyclomatic complexity meaningfully, and it would make each responsibility independently testable.


jsonable_encoder — fastapi/encoders.py

jsonable_encoder
fastapi/encoders.py
17.77
critical
CC 51
ND 4
FO 24
touches/30d 1

jsonable_encoder is fastapi’s central serialization function — it takes arbitrary Python objects and converts them into JSON-serializable form, handling Pydantic models, dataclasses, enums, decimals, and more. The name alone signals breadth of responsibility, and the metrics confirm it.

Cyclomatic Complexity 51
threshold: 10

A cyclomatic complexity of 51 means 51 independent execution paths through a single function. That is not moderate complexity — it is extreme. Each path represents a type of input the function has to handle correctly, and each is a potential surface for a regression when the function is touched. The hub_function pattern flag adds another dimension: this function is a central dispatch point that many callers depend on, meaning a change here ripples across everything that serializes a response.

The external signals on this file are the most striking of any hotspot in this analysis: pr_review_comment_density of 18.0 on a single-commit file means reviewers have left an unusually high volume of comments relative to the commit history. That is consistent with a function whose behavior is non-obvious and where the edge cases require careful scrutiny. There are no bug-linked commits or reverts, so this is not a historically defect-prone file — but the review density suggests the complexity is already creating friction for contributors.

The exit_heavy pattern compounds the testing challenge: with 51 branches and multiple exit points, achieving meaningful coverage requires a very large test matrix. My recommendation: introduce a type-dispatch table or a chain of single-responsibility encoder functions (one per major type category), and reduce jsonable_encoder to an orchestrator that delegates to them. This would cut the cyclomatic complexity dramatically and make each type handler independently testable.


remove_unused_docs_src — scripts/docs.py

remove_unused_docs_src
scripts/docs.py
17.45
critical
CC 64
ND 5
FO 21
touches/30d 1

This function lives in the documentation tooling layer rather than the core framework, but its metrics place it third overall — and the numbers warrant attention before the next docs infrastructure push.

Cyclomatic Complexity 64
threshold: 10

A cyclomatic complexity of 64 in a docs script is surprising. From the name and path, this function appears to traverse documentation source directories and remove files or sections that are no longer referenced. That kind of filesystem traversal and cross-reference logic can accumulate branches quickly: checking file existence, matching path patterns, handling different doc formats, guarding against partial states. The nesting depth of 5 reinforces that the control flow is layered — conditions nested inside loops nested inside other conditions.

The deeply_nested, complex_branching, and exit_heavy pattern flags all appear here. The pr_review_comment_density for this file is 0.0, which means it has not attracted reviewer scrutiny — consistent with tooling scripts that tend to be reviewed less rigorously than framework code. That makes the structural complexity more concerning: there is no compensating signal that reviewers have been checking the edge cases.

My recommendation: decompose this into smaller functions, each responsible for one step of the cleanup pipeline — enumerate candidates, validate references, delete confirmed orphans. A cyclomatic complexity of 64 in a script that modifies files is a meaningful blast-radius risk if the logic misfires.


main — scripts/deploy_docs_status.py

main
scripts/deploy_docs_status.py
15.72
critical
CC 42
ND 3
FO 36
touches/30d 1

This is the entry-point function for the docs deployment status script. By name and location, it almost certainly orchestrates the full deployment status check — polling external services, parsing responses, making conditional decisions about deployment state, and reporting results.

Fan-out 36
threshold: 15

A fan-out of 36 in a main function is a strong god-function signal: this single entry point calls into 36 distinct functions, making it a coordination hub for the entire script. Combined with a cyclomatic complexity of 42 — placing it well into the high-risk range — the function is doing far more than orchestrating: it is encoding significant decision logic inline. The exit_heavy and long_function patterns confirm that the function branches heavily and has grown beyond what can be reasoned about in a single pass.

The nesting depth of 3 is the lowest among the top five, which is the one structural mitigant here. But with no reviewer comments on record and a single author in the last 90 days, this function carries single-point-of-ownership risk alongside its complexity. My recommendation: extract the status-check logic, the conditional deployment decisions, and the reporting steps into named functions, leaving main as a thin sequencer. This would reduce the cyclomatic complexity to single digits and make the deployment logic auditable independently of the orchestration.


get_openapi_path — fastapi/openapi/utils.py

get_openapi_path
fastapi/openapi/utils.py
15.45
critical
CC 77
ND 6
FO 44
touches/30d 1

get_openapi_path generates the OpenAPI path item object for a single route — resolving parameters, request bodies, response schemas, security requirements, and operation metadata. The scope of that responsibility explains the metrics.

Cyclomatic Complexity 77
threshold: 10

A cyclomatic complexity of 77 is the highest in the top five and places this function in extreme territory. Each of those 77 paths corresponds to a combination of route configuration options that the function must handle correctly: optional versus required parameters, different body types, multiple response codes, security schemes, deprecated flags. The nesting depth of 6 means some of those branches are deeply embedded in control structures that require holding a significant amount of context to follow.

The fan-out of 44 means the function reaches into 44 distinct callees — schema resolution utilities, parameter extraction helpers, response model processors. In Python, where many of those calls may dispatch through Pydantic’s dynamic model machinery, the actual coupling surface is likely wider than the static count shows. The deeply_nested, complex_branching, and god_function patterns all appear, and the long_function flag signals there is simply too much happening in one place.

There are no bug-linked commits or reverts on this file, and reviewer comment density is 0.0 — so the historical signal is quiet. But a CC of 77 with recent changes (days_since_changed: 16) is a structural condition where a well-intentioned fix to one OpenAPI generation case can silently break another. My recommendation: identify the major generation responsibilities — parameter resolution, request body construction, response schema assembly, security annotation — and extract each into its own testable function. The goal is to reduce get_openapi_path to a compositor that calls those extracted units in sequence, targeting a cyclomatic complexity below 15.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
god_function5
long_function5
complex_branching4
exit_heavy4
deeply_nested3
hub_function1

These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.

See more analyses with these patterns: complex_branching, deeply_nested, exit_heavy, god_function, long_function.

Reproduce This Analysis

git clone https://github.com/tiangolo/fastapi
cd fastapi
git checkout 3f3354a94d7c8b496258d7b762070e46704e01c1
hotspots analyze . --mode snapshot --explain-patterns --force

To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.

I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →

Was this useful? Let me know →

Related Analyses