Function-Level Cycles
Function-Level Cycles
Section titled “Function-Level Cycles”Arxo can detect cycles not only between modules (imports) but also between functions (calls). Function-level cycles show where runtime call chains form loops — useful when module-level analysis is clean but you still have tight coupling inside a file or across a few files.
For an overview of circular dependencies, see Circular Dependencies.
Module vs Function Level
Section titled “Module vs Function Level”| Level | What it detects | Use when |
|---|---|---|
| Module | Import cycles (A imports B, B imports A) | You want to fix dependency structure, tree-shaking, builds |
| Function | Call cycles (f1 calls f2, f2 calls f1) | You want to find circular call chains, refactor within a module |
Function-level analysis requires a call graph. Arxo builds the call graph when the language and config support it (TypeScript/JavaScript, Rust, Python, Java, etc.). If the call graph is not available, only module-level SCC metrics are reported.
What It Measures
Section titled “What It Measures”| Metric | What it tells you | Ideal |
|---|---|---|
scc.function.component_count | Number of independent call-graph components | High (= function count) |
scc.function.max_cycle_size | Size of the largest function-level cycle | 0 (no call cycles) |
scc.function.total_nodes_in_cycles | Functions stuck in call cycles | 0 |
scc.function.call_mass | Total call volume (call_count) inside cycles | 0 |
scc.function.cycle_cut_candidates_count | Number of suggested edges to break call cycles | — |
Call mass is the sum of call_count on edges inside cycles. Higher call mass means more runtime traffic flows through circular calls — a stronger signal to refactor.
Cycle-Cut Candidates (Function Level)
Section titled “Cycle-Cut Candidates (Function Level)”When the call graph is available, Arxo suggests which call edge to remove to break each function-level cycle. Each candidate has:
- from / to — the caller and callee (function or node IDs)
- call_count — how often that call appears; lower = cheaper to break
Start with the edge that has the lowest call_count — it’s the least-used dependency and usually the easiest to remove or invert.
{ "cycle_cut_candidates": [ { "from": "src/document/builders/join.js::buildJoin", "to": "src/document/utilities/assert-doc.js::assertDoc", "call_count": 2, "note": "Cheapest edge to cut in this cycle" } ]}When to Use Function-Level Analysis
Section titled “When to Use Function-Level Analysis”- Module-level is clean — No import cycles, but code still feels tangled. Function-level cycles can explain it.
- Refactoring inside a module — You want to break circular calls between functions or classes.
- Runtime coupling — High
call_massmeans a lot of execution flows through cycles; fixing them can simplify control flow and testing.
Enabling and Output
Section titled “Enabling and Output”Function-level metrics appear in the SCC result when the engine has built a call graph. No extra config is required beyond enabling the scc metric; if the language supports call analysis, scc.function.* and cycle_cut_candidates will be populated.
To inspect candidates:
arxo analyze --format json | jq '.ui_schemas.scc.issues.categories.cycle_cut_candidates.critical'Next Steps
Section titled “Next Steps”- Interpretation — How to read cycle results and thresholds
- Fixing Cycles — Step-by-step process (applies to both module and function levels)
- Refactoring Patterns — Patterns that break cycles
- Circular Dependencies — Back to overview