Three of the top five hotspots in go-chi/chi live in one file: tree.go. That’s not a coincidence — it means the routing engine’s core matching logic is concentrated risk, not scattered risk. The top function, findRoute, carries an activity-weighted risk score of 17.18 and sits in the ‘debt’ quadrant: 74 branches deep with 7 levels of nesting, but zero touches in the last year and 40 days since its last change. Across 245 analyzed functions, 20 sit in the critical band, and this analysis covers the five that matter most right now. I’d frame the priority here as blast radius, not urgency — this is code that will be expensive to change safely whenever someone next has to touch it.
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
| Function | File | Risk | CC | ND | FO |
|---|---|---|---|---|---|
findRoute | tree.go | 17.2 | 74 | 7 | 10 |
patNextSegment | tree.go | 14.7 | 34 | 5 | 4 |
ThrottleWithOpts | middleware/throttle.go | 12.3 | 21 | 2 | 10 |
NewCompressor | middleware/compress.go | 11.4 | 17 | 4 | 8 |
findPattern | tree.go | 11.2 | 19 | 3 | 6 |
245 functions analyzed
The quadrant split is the first thing worth sitting with: 80 functions in ‘debt’ versus only 5 in ‘fire’. Most of the structural risk here isn’t from code churning right now — it’s dormant complexity waiting for the next feature request or bug report to force someone into it. chi is a routing library; its core value proposition is fast, correct path matching, and that’s exactly where the debt is concentrated.
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.Exit Heavy×3Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.Deeply Nested×2Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.God Function×2God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.Long Function×2Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.Stale Complex×2Stale Complex
High structural complexity but untouched for a long time — structural debt that will bite whoever opens it next.
findRoute — tree.go
This is the trie-walking route matcher at the center of chi’s request dispatch — the function that decides which handler answers an incoming request. A cyclomatic complexity of 74 with nesting 7 levels deep is the highest combination in the dataset, and the source confirms why: a switch over node types (static, param, regexp, catch-all) with a nested loop-and-continue pattern searching sibling nodes, multiple early returns once a matching endpoint is found, and inline handling of the ‘method not allowed’ case. The ‘exit_heavy’ and ‘god_function’ tags are earned here — I count returns embedded at three different nesting levels in the excerpt alone, each one a distinct test case a reviewer has to reason through.
It hasn’t been touched in 40 days and shows zero commits in the last year, so this isn’t a live regression risk today — it’s the highest blast-radius function in the repo for whenever the next routing change lands. The file’s history shows a bug-fix rate of 8.33% across 12 total commits and 3 distinct authors in the last 90 days, and a fairly high review-comment density (1.67), which tells me past changes here have drawn real scrutiny, not been rubber-stamped. My recommendation: before the next routing feature touches this function, extract the per-node-type matching logic (static / param-regexp / catch-all) into three separate helper functions. That alone would cut the branching a reviewer has to hold in their head at once without changing behavior.
patNextSegment — tree.go
This one parses a route pattern segment — splitting out {param}, {param:regexp}, and * wildcard syntax — and it’s the most dormant complex function in the set: 602 days since its last change. The complexity comes from manually walking brace characters to find a matching } (tracking an open/close counter), then branching on regexp-vs-param, then validating wildcard placement with two separate panic calls for malformed patterns. That’s a hand-rolled mini-parser with cyclomatic complexity 34 and nesting depth 5, sitting in the same ‘debt’ quadrant as findRoute — same file, same low bug-fix history (8.33% across the shared 12-commit, 3-author history), same zero-touches-but-high-consequence profile.
The practical risk here is that a parsing bug in route-pattern syntax would stay silent until someone registers an unusual route. My recommendation: this is a strong candidate for table-driven unit tests covering every branch — regexp param, plain param, tail delimiter variations, and both panic conditions — before anyone refactors it, since the current test coverage isn’t visible in this data and the panic-based validation makes untested edge cases genuinely risky.
ThrottleWithOpts — middleware/throttle.go
This is the constructor for chi’s throttling middleware, and it’s a genuinely different risk shape than the tree.go trio — it’s Go concurrency code, not tree traversal. The excerpt shows a nested select over four channel cases (context cancellation, backlog token, immediate token, timeout) with a deferred token-return in three separate branches. Cyclomatic complexity of 21 is moderate on paper, but for concurrent code that number understates the real risk: reasoning about channel interleaving and goroutine scheduling isn’t captured by branch counts alone, and a subtle change to one select arm can shift ordering guarantees that structural metrics can’t see.
It’s ‘debt’ quadrant with zero touches in the last year and 399 days since last change, and the file’s bug-fix rate of 25% (from only 4 total commits) is the highest bug-fix ratio in this top five, though on a small commit sample. Fan-out of 10 — matching findRoute’s — means this function coordinates a lot of moving pieces: token channels, timers, and the wrapped handler. My recommendation: if this needs a change, pair it with a concurrency-focused test (race detector on, deliberately delayed contexts) rather than relying on the existing test suite’s shape, since channel-based logic like this is exactly where unit tests can pass while race conditions slip through.
NewCompressor — middleware/compress.go
This is the constructor for chi’s response-compression middleware, building the allowed content-type set and registering default encoders (deflate, gzip). The complexity here is mostly straightforward branching — wildcard content-type validation with two panic guards, a fallback to a default type list, then sequential encoder registration — giving cyclomatic complexity 17 and nesting 4, tagged simply as ‘complex_branching’ without the exit-heavy or god-function markers seen elsewhere in this list.
What stands out is the file-level history: a bug-fix rate of 54.55% across 11 commits, plus one recorded revert, the highest of any function in this top five. That’s a meaningful signal — not proof this specific function is defective, but strong evidence that middleware/compress.go as a file has had a rockier maintenance history than tree.go or throttle.go. Combined with 3 distinct authors in the last 90 days and a review-comment density of 2.25 (also the highest here), this reads as an area that’s drawn disagreement or correction before. This function itself sits at 40 days since its last change, so it’s dormant like the rest of the list, but the file around it has been busier than tree.go or throttle.go historically. My recommendation: if you’re touching this function, check recent closed issues and PRs against this file specifically before assuming the wildcard-matching logic is settled.
findPattern — tree.go
The third tree.go entry, and the most dormant function in the entire top five: 1,073 days since it last changed. It’s a recursive pattern-matching walk — the function calls itself (n.findPattern(xpattern)) after slicing off a matched segment, with a switch over node type and a panic on an unrecognized type. Recursion plus a four-way switch is why cyclomatic complexity lands at 19 with fan-out of 6, even though nesting is comparatively shallow at 3.
Three years without a change, on code this central to routing, tells me this logic has proven stable — but it also means nobody currently holds the full mental model of its edge cases fresh in mind. If a change to findRoute or patNextSegment ever needs a matching update here, budget real review time, not a quick pass.
Taken together, these three functions make tree.go the file I’d flag first for anyone planning routing changes in chi — not because any one function is broken, but because 60% of the current critical-risk list sits behind the same set of imports and shared node types. A change to the trie structure has to be checked against all three.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
complex_branching | 3 |
exit_heavy | 3 |
deeply_nested | 2 |
god_function | 2 |
long_function | 2 |
stale_complex | 2 |
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, stale_complex.
Reproduce This Analysis
git clone https://github.com/go-chi/chi
cd chi
git checkout 167e1e3bd039d060696b99c8da4e876ae04f42c1
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 →