Skip to content
Arxo Arxo

Language-Specific Patterns

Circular dependencies can look different depending on the language: import style, barrel files, and runtime behavior all affect where cycles appear and how to fix them. Arxo detects cycles across TypeScript/JavaScript, Rust, Python, Java, Kotlin, and Go. This page summarizes common patterns and fixes per language.

For an overview, see Circular Dependencies. For refactoring patterns that work in any language, see Refactoring Patterns.

  • Barrel filesindex.ts re-exports from many files, and those files import from index.ts, forming A → index → B → index → A.
  • Types and values — File A imports a type and a value from B, B imports from A; the runtime import creates the cycle even if the type is only used in type position.
  • Circular component trees — e.g. React components that import each other through a long chain back to the start.
  • Prefer direct imports to the concrete file instead of the barrel when that would break the cycle.
  • Extract shared types into a types.ts (or similar) that neither side imports from the other — see Refactoring Patterns: Move Types to Definitions File.
  • Split barrels so that the cycle is broken (e.g. one barrel for “public” API, no circular re-exports).

Cycles prevent reliable tree-shaking. If bundle size matters, fixing import cycles is a prerequisite.


  • Circular imports — Module A imports B, B imports A (or A → B → C → A). Python may load them at runtime but with fragile order and “partial module” issues.
  • Type hintsfrom __future__ import annotations and forward references can hide circular imports at runtime but the static graph still has a cycle.
  • Shared models — Two modules that define or use each other’s types or models.
  • Dependency injection or late imports (import inside a function) can break runtime cycles; Arxo still sees the static import graph, so prefer removing the static cycle (e.g. move shared code to a third module).
  • Extract shared code into a separate module both A and B import from — see Refactoring Patterns: Extract Shared Module.
  • Use a types-only module or TYPE_CHECKING imports so that the main execution path doesn’t form a cycle.

  • Circular use — Crate or module A uses B, B uses A. Rust’s module system allows it in some cases but it can hurt compile times and clarity.
  • Feature flags and conditional compilation — Cycles might appear only for certain feature combinations; run Arxo with the same config you care about.
  • Extract a common crate that both sides depend on (types, traits, or shared logic).
  • Dependency inversion — depend on traits in a shared crate, implement in the crates that currently depend on each other.
  • Split modules so that the dependency direction is one-way (e.g. core → app, not app ↔ core).

  • Package cycles — Package P1 has a class that depends on P2, and P2 depends on P1.
  • Circular service/component references — e.g. Spring beans or Koin modules that inject each other.
  • Extract an interface or API module that one side depends on and the other implements.
  • Introduce an abstraction layer (e.g. events or a facade) so that the high-level package doesn’t depend on the low-level one for the cyclic part.
  • Enforce package layering in CI (e.g. with Arxo policy) so that new cycles are rejected.

  • Import cycles — Package A imports B, B imports A. The compiler rejects direct cycles, but longer chains (A → B → C → A) are possible.
  • Test packagespackage_test importing the main package and the main package (or its deps) importing test helpers can create cycles in the full graph.
  • Extract shared types or interfaces into a separate package both sides import.
  • Dependency injection — pass interfaces or structs instead of importing the package that would close the cycle.
  • Go’s compiler already forbids direct two-way imports; Arxo helps you find and break longer cycles and keep the graph clean over time.

  • Group by folder or package — Use group_by: folder (or equivalent) in Arxo so that cycles are reported at the level you care about (e.g. packages or modules, not every single file).
  • Fix small cycles first — Two- or three-node cycles are usually easier to break and still improve metrics; see Fixing Cycles.
  • Use the same grouping in CI — So that “no new cycles” is enforced at the same granularity you use locally.