Circular Dependencies
Circular Dependencies
Section titled “Circular Dependencies”Overview
Section titled “Overview”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).
What It Measures
Section titled “What It Measures”Module-Level
Section titled “Module-Level”| Metric | What it tells you | Ideal |
|---|---|---|
scc.component_count | Number of strongly connected components (higher = healthier) | Equal to total module count |
scc.cycle_count | Number of non-trivial cycles (SCC size > 1) | 0 |
scc.max_cycle_size | Size of the largest cycle (in modules) | 0 |
scc.total_nodes_in_cycles | How many modules are stuck in cycles | 0 |
Function-Level
Section titled “Function-Level”When call-graph analysis is available, cycles are also detected at the function level:
| Metric | What it tells you | Ideal |
|---|---|---|
scc.function.component_count | Number of call-graph components | High (= function count) |
scc.function.cycle_count | Number of function-level cycles | 0 |
scc.function.max_cycle_size | Largest function-level cycle | 0 |
scc.function.total_nodes_in_cycles | Functions stuck in call loops | 0 |
scc.function.call_mass | Runtime call traffic flowing through cycles | 0 |
scc.function.cycle_cut_candidates_count | Number of suggested edges to break call cycles | — |
Detailed Output
Section titled “Detailed Output”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-nodechurnandhotspot_score
For the complete key contract, see SCC Keys and Output Contract.
Why It Matters
Section titled “Why It Matters”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
In-Depth Guides
Section titled “In-Depth Guides”| Guide | What you’ll learn |
|---|---|
| Interpretation | How to read cycle results, set thresholds, and prioritize work |
| Cycle Microscope | Per-cycle details, node table, boundary edges, and refactor priority |
| Function-Level Cycles | Detect call-graph cycles and use function-level cut candidates |
| Git History Integration | Enable churn and hotspot ranking to prioritize fixes |
| Fixing Cycles | Step-by-step process to identify and break cycles |
| Refactoring Patterns | Extract shared modules, dependency injection, interface layering |
| Policy | Enforce cycle limits in CI (strict, pragmatic, gradual) |
| Keys and Output Contract | Config options, emitted keys, UI schema fields, findings contract |
| Performance | Benchmarks and comparison with other tools |
| CLI and Integration | CLI usage, pre-commit hooks, GitHub Actions, IDE |
| Cross-Metric Impact | How fixing cycles improves other metrics |
| Language-Specific Patterns | Common TS/JS and Python cycle patterns and fixes |
Related Metrics
Section titled “Related Metrics”- Change Impact: cycles increase change ripple
- Critical Modules: cycle members are often highly central
- Module Boundaries: cycles blur modular boundaries
- Layer Structure: cycles weaken directional layering
Best Practices
Section titled “Best Practices”- Aim for zero new cycles in incoming changes.
- Fix small cycles first to build steady progress.
- Use cut candidates to choose lower-risk edges first.
- Gate in CI so cycles do not regress.
- Track trend over time with baselines and snapshots.
- Prioritize high-hotspot nodes when git history is enabled.