Skip to content
Arxo Arxo

Circular Dependencies

The Circular Dependencies metric finds import cycles: modules that depend on each other directly or indirectly, forming a loop. Cycles are a common structural problem in growing codebases: they create tight coupling, complicate testing, and make refactoring risky.

A cycle exists when module A imports B, B imports C, and C imports A (or any longer chain that loops back).

MetricWhat it tells youIdeal
scc.component_countNumber of strongly connected components (higher = healthier)Equal to total module count
scc.cycle_countNumber of non-trivial cycles (SCC size > 1)0
scc.max_cycle_sizeSize of the largest cycle (in modules)0
scc.total_nodes_in_cyclesHow many modules are stuck in cycles0

When call-graph analysis is available, cycles are also detected at the function level:

MetricWhat it tells youIdeal
scc.function.component_countNumber of call-graph componentsHigh (= function count)
scc.function.cycle_countNumber of function-level cycles0
scc.function.max_cycle_sizeLargest function-level cycle0
scc.function.total_nodes_in_cyclesFunctions stuck in call loops0
scc.function.call_massRuntime call traffic flowing through cycles0
scc.function.cycle_cut_candidates_countNumber of suggested edges to break call cycles

In JSON and UI output you also get:

  • Cycle details (scc_details): nodes, internal edges, boundary edges, witness path, risk score
  • Cycle-cut candidates: candidate dependency edges ranked for easier cuts
  • Structured SCC entries: scc.cycle_summary, scc.cut_candidates, scc.cycles, scc.top_cycles_by_risk, scc.top_cut_candidates
  • Condensed graph: scc.condensed_dag
  • Hotspot data (with use_git_history: true): per-node churn and hotspot_score

For the complete key contract, see SCC Keys and Output Contract.

Circular dependencies:

  • Block independent testing: you cannot test one module without pulling in the entire cycle
  • Reduce bundling efficiency: cycles can limit dead-code elimination
  • Slow structural change: touching one file can force coordinated edits across the cycle
  • Increase refactor risk: unclear dependency direction causes regressions
  • Hide across long paths: large cycles are hard to spot without graph analysis
GuideWhat you’ll learn
InterpretationHow to read cycle results, set thresholds, and prioritize work
Cycle MicroscopePer-cycle details, node table, boundary edges, and refactor priority
Function-Level CyclesDetect call-graph cycles and use function-level cut candidates
Git History IntegrationEnable churn and hotspot ranking to prioritize fixes
Fixing CyclesStep-by-step process to identify and break cycles
Refactoring PatternsExtract shared modules, dependency injection, interface layering
PolicyEnforce cycle limits in CI (strict, pragmatic, gradual)
Keys and Output ContractConfig options, emitted keys, UI schema fields, findings contract
PerformanceBenchmarks and comparison with other tools
CLI and IntegrationCLI usage, pre-commit hooks, GitHub Actions, IDE
Cross-Metric ImpactHow fixing cycles improves other metrics
Language-Specific PatternsCommon TS/JS and Python cycle patterns and fixes
  1. Aim for zero new cycles in incoming changes.
  2. Fix small cycles first to build steady progress.
  3. Use cut candidates to choose lower-risk edges first.
  4. Gate in CI so cycles do not regress.
  5. Track trend over time with baselines and snapshots.
  6. Prioritize high-hotspot nodes when git history is enabled.