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 the most common structural problem in growing codebases: they create tight coupling, complicate testing, and make refactoring painful.
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.max_cycle_size | Size of the largest cycle (in modules) | 0 (no cycles) |
scc.total_nodes_in_cycles | How many modules are stuck in cycles | 0 |
scc.component_count | Number of independent groups (higher = healthier) | Equal to total module count |
Function-Level
Section titled “Function-Level”When call graph analysis is enabled, cycles are also detected at the function level:
| Metric | What it tells you | Ideal |
|---|---|---|
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 | How much runtime traffic flows through cycles | 0 |
scc.function.cycle_cut_candidates_count | Suggested edges to break | — |
Detailed Output
Section titled “Detailed Output”In JSON/UI output you also get:
- Cycle details: Which files are in each cycle, internal edges, and boundary connections
- Cycle-cut candidates: The cheapest edges to remove to break each cycle
- Hotspot data (with
use_git_history: true): Which files in cycles are changed most often — fix those first
Why It Matters
Section titled “Why It Matters”Circular dependencies:
- Block independent testing — you can’t test one module without pulling in the whole cycle
- Prevent tree-shaking — bundlers can’t remove unused code inside cycles
- Slow down builds — compilers can’t parallelize modules in a cycle
- Make refactoring risky — changing one file in a cycle can break all the others
- Hide behind indirection — cycles through 10+ modules are invisible without tooling
In-Depth Guides
Section titled “In-Depth Guides”| Guide | What you’ll learn |
|---|---|
| Interpretation | How to read cycle results, set thresholds, and use cycle-cut candidates |
| Cycle Microscope | Per-cycle details, node table, boundary edges, and refactor priority |
| Function-Level Cycles | Detect call-graph cycles and use function-level cycle-cut candidates |
| Git History Integration | Enable churn and hotspot scores to prioritize which files to fix first |
| Fixing Cycles | Step-by-step process to identify and break cycles |
| Refactoring Patterns | Extract shared modules, dependency injection, types files |
| Policy | Enforce cycle limits in CI (strict, pragmatic, or gradual) |
| 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 Layer Structure, Change Impact, and other metrics |
| Language-Specific Patterns | How cycles show up in TypeScript, Python, Rust, Java, Go, and how to fix them |
Related Metrics
Section titled “Related Metrics”- Change Impact — Cycles dramatically increase how far changes ripple
- Critical Modules — Cycles often involve the most connected modules
- Module Boundaries — Cycles reduce modularity score
- Layer Structure — Cycles break clean layering
Best Practices
Section titled “Best Practices”- Aim for zero cycles in new code
- Fix small cycles first — 2-3 module cycles are quick wins
- Use cycle-cut candidates — start with the edge that has the lowest call count
- Enable in CI — prevent new cycles from being introduced
- Track over time — use baseline comparison to measure progress
- Fix high-churn files first — prioritize files that change often (enable git history)
- Document exceptions — if a cycle must stay, explain why
Background
Section titled “Background”Cycle detection uses Tarjan’s algorithm (1972), which finds all cycles in linear time. In the Prettier codebase (4,883 files), Arxo detected a 56-module cycle that other tools missed entirely — see Performance for details.
Research shows that circular dependencies increase maintenance costs by 40-60% and correlate with higher defect rates.