TanStack/query has 84 critical functions; persistence and codemod layers show the highest activity-weighted risk.

Experimental persistence API and v5 codemod transformers combine high cyclomatic complexity (14–26) with recent churn; 5 of top 5 hotspots are exit-heavy.

Stephen Collins ·
oss typescript refactoring code-health

Antipatterns Detected

exit_heavy 5 complex_branching 4 deeply_nested 4 god_function 4 long_function 4

Top pattern: exit_heavy

Key Points

What is exit_heavy and why does it matter in query?

Exit_heavy functions have multiple return, break, or throw statements scattered throughout—each is another path that's hard to trace. In query's persistence layer, this makes it difficult to predict which code path executes and why, increasing bug risk when the code changes.

How do I reduce cyclomatic complexity in TypeScript?

Extract branching logic into smaller helper functions, use early returns to flatten nesting, and replace long if-else chains with lookup objects or switch statements. Start with query's createQueryPersister—extract the filter logic and restore logic into separate utilities.

Is query actively maintained?

Yes. The activity-weighted risk scores indicate recent commits to persistence and codemod code, meaning these are live areas of development. Active maintenance on complex code is exactly when refactoring pays off most.

How do I reproduce this analysis?

Use the hotspots CLI against the TanStack/query repository to generate a risk report at the same commit SHA; the tool will surface these same functions and patterns.

What does activity-weighted risk mean?

It combines cyclomatic complexity (how many branches a function has) with recent commit frequency. Functions that are both hard to understand AND actively changing are the highest priority to refactor, because they're most likely to harbor bugs.

TanStack/query’s critical risk is concentrated in two layers: the experimental query persistence API and the v5 codemod infrastructure. Across 3,031 total functions, 84 are flagged critical; the top 5 hotspots all occupy the fire quadrant (complex and actively changing), with activity_risk scores between 15.44 and 16.82. Four of the five exhibit deeply nested control flow (ND 5–6) and exit-heavy patterns—multiple return paths that increase test-case burden and regression risk.

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
experimental_createQueryPersisterpackages/query-persist-client-core/src/createPersister.ts16.814627
<anonymous>…/query-codemods/…/filter-aware-usage-transformer.cjs16.323520
<anonymous>…/query-codemods/…/filter-aware-usage-transformer.cjs15.711530
restoreQueriespackages/query-persist-client-core/src/createPersister.ts15.61968
Explorerpackages/query-devtools/src/Explorer.tsx15.426338

Codemod / Tooling Files in Results

Rows 2 and 3 in the table above are two distinct anonymous functions inside packages/query-codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.cjs. This is a codemod tooling file—not core query logic—that scores high due to the structural complexity of AST transformation (CC 23 and 11, fan-out 20 and 30). Codemods are maintenance-time tools; high CC here reflects the combinatorial logic of matching and rewriting AST nodes, not production risk. If you want to focus your analysis on core library code, add this to .hotspotsrc.json: "exclude": ["packages/query-codemods/**"].

Hotspot Analysis

experimental_createQueryPersister — packages/query-persist-client-core/src/createPersister.ts

This experimental persistence factory has the highest activity_risk (16.82) and sits squarely in the fire quadrant. Its CC of 14 indicates moderate-to-high branching complexity; the ND of 6 reveals deeply nested control structures that make reasoning about state transitions difficult. With a fan-out of 27, changes here have broad coupling implications across the persistence subsystem. The experimental_ prefix signals this API is not yet stable—high activity combined with structural complexity means the team is iterating on logic that affects query hydration and cache restoration.

Recommendation: Before refactoring, map the 27 callees to identify which ones handle critical paths (e.g., cache validation, retry logic). Extract independent orchestration steps into smaller, testable functions; in particular, isolate branching logic that decides whether to restore vs. discard persisted state. Add characterization tests to lock down current behavior before simplifying nesting depth.

restoreQueries — packages/query-persist-client-core/src/createPersister.ts

restoreQueries runs at app initialization: it reads serialized query state from the persistence backend (IndexedDB, localStorage, etc.) and decides which cached results are still valid to hydrate back into the QueryClient. CC 19 means 19 independent decision paths through that initialization logic—every branch is a different answer to “is this persisted query still usable?” Max nesting depth of 6 makes those decisions hard to follow in sequence. Despite lower fan-out (8), the exit-heavy pattern scatters the success/failure/expiry validation across multiple early returns rather than concentrating it at the top.

Recommendation: Extract validation and recovery logic into named helper functions (e.g., isRestoredQueryValid, applyRestoredQueryState). Replace early returns with a clear state machine or decision table at the top level. This will reduce ND and make test coverage for each recovery path explicit.

Explorer — packages/query-devtools/src/Explorer.tsx

The Explorer component is a god_function in the devtools layer: CC 26 indicates many conditional branches, and fan-out of 38 means it orchestrates broadly across the devtools UI. Unlike the persistence functions, its ND is moderate (3), but the combination of high CC with 38 distinct function calls—plus recent churn (activity_risk 15.44)—suggests it is a hub for state inspection and mutation. The exit-heavy pattern compounds the coupling risk: changes to branching logic ripple across many dependents.

Recommendation: Decompose by feature or inspection mode: separate query list rendering, filter logic, and cache inspection into isolated child components or hooks. This will reduce fan-out and CC simultaneously. Add snapshot tests for each mode before refactoring to catch unintended UI regressions.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
exit_heavy5
complex_branching4
deeply_nested4
god_function4
long_function4

That all five top hotspots share this exact cluster is itself a finding: structural debt isn’t isolated to one poorly-written function — it’s a consistent style across the persistence, codemod, and devtools layers simultaneously.

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.

Key Takeaways

  • experimental_createQueryPersister and restoreQueries both have activity_risk > 15 and ND ≥ 6; prioritize reducing nesting depth in the persistence layer before the API graduates from experimental status.
  • The Explorer devtools component couples 38 distinct callees with CC 26; extract presentation logic into focused subcomponents to isolate UI changes from state-inspection refactors.
  • All 5 top hotspots exhibit exit-heavy patterns (multiple returns); introduce guard clauses and decision points at function entry to concentrate control flow, which will also improve test coverage clarity.

Reproduce This Analysis

git clone https://github.com/TanStack/query
cd query
git checkout 391db367b3e7adeff9a0dd647474d1575a31a787
hotspots analyze . --mode snapshot

Hotspots highlights structural and activity risk — not “bad code.” Findings are a prioritization aid, not a bug predictor. Editorial policy →

Related Analyses