Circular Dependency Detector
Where are the import cycles in your code — and which import breaks them?
To find every circular dependency, paste your import/require statements (or a madge JSON, DOT graph or edge list) and this tool builds the import graph and runs Tarjan's strongly-connected-components algorithm to locate every cycle — tracing the exact path (A → B → C → A), drawing it on an interactive arc diagram, scoring how acyclic your graph is, and — the part other tools leave to you — recommending the single import to cut to break each cycle. JavaScript, TypeScript, Python, Java, C# and Go, all parsed and analysed in your browser.
Cycle detection via Tarjan's algorithm (linear-time SCC) · cut recommendations via a greedy minimal-feedback-arc heuristic · your code never leaves your machine
Circular dependency console
Why a circular dependency is worth breaking
In JavaScript and Python a cycle often runs fine until the import order shifts — then you get an undefined export or Python's 'partially initialized module' error in production, far from where the cycle lives.
A bundler can't cleanly separate modules that depend on each other, so a cycle can force unrelated code into the same chunk — quietly inflating the bundle the user downloads.
If A needs B and B needs A, you can't load one without the other, so mocking breaks down and 'unit' tests quietly become integration tests with hidden coupling.
Strongly connected components grow: a 2-module cycle that's left alone tends to accrete a third and fourth module until the whole region is a tangle. The cheapest time to cut a cycle is the day it appears.
How cycles bite — symptom, cause & fix by language
| Language | Symptom | Cause | Fix |
|---|---|---|---|
| JavaScript / TS | Imported value is undefined at module init | ESM/CJS cycle: the importing module runs before the imported one finishes evaluating | Move the shared piece to a third module, or defer access (import inside the function, not at top level). |
| Python | ImportError: cannot import name … (partially initialized module) | Two modules import each other at top level | Import inside the function, merge the modules, or extract the shared symbols into a new module both import. |
| TypeScript | 'X' implicitly has type 'any' / used before declaration | Type-level cycle between modules | Use `import type` (erased at runtime) or hoist shared types into a types-only module. |
| Java | Compiles, but Spring throws BeanCurrentlyInCreationException | Two beans constructor-inject each other | Use setter/field injection or @Lazy on one side; better, introduce a mediator that both depend on. |
| Go | import cycle not allowed (build fails outright) | Go forbids package import cycles by design | Extract the shared types/interfaces into a new package that both import, inverting the dependency. |
| C# | Runtime null from a static initializer | Static constructors referencing each other across a cycle | Break the static coupling with an interface and dependency injection. |
| Any | Bundle won't tree-shake / chunks merge unexpectedly | Cyclic modules can't be separated by the bundler | Cut the cycle; the bundler can then split the modules independently. |
| Any | Refactor 'touches everything' | A strongly connected component has no safe edit order | Make the region acyclic first (cut the back-edges below), then the refactor localises. |
How circular-dependency tools compare
The CLIs are the right call in CI; this is the zero-install companion that adds the arc diagram and the recommended cut.
| Tool | Runs | Input | Languages | Visualization |
|---|---|---|---|---|
| This tool | In your browser | Imports, madge JSON, DOT, edge list | JS/TS, Py, Java, C#, Go | Arc diagram + cut edges |
| madge | Node CLI | Your source tree | JS/TS | Graphviz image |
| dependency-cruiser | Node CLI | Your source tree | JS/TS | Graph + rules |
| eslint-plugin-import | Lint step | Source, per-file | JS/TS | Lint errors only |
| Go vet / compiler | Build step | Go packages | Go | Build error only |
Pair this with the npm dependency analyzer for the install graph behind these imports.
How detection works
Acyclicity score
acyclicity = 100 × (1 − modules in cycles / total modules)Worked: 4 of 12 modules caught in cycles → 100 × (1 − 4/12) = 67% → “Heavily tangled”. 100% is a clean DAG.
- ≥100%Fully acyclic
- ≥92%Nearly clean
- ≥75%Tangled
- ≥0%Heavily tangled
The algorithm
- 1. Parse imports into a directed graph (module → module it imports). External packages are dropped — they can't close a cycle back into your code.
- 2. Run Tarjan's algorithm to find every strongly-connected component in linear time. Any SCC larger than one node is a tangle.
- 3. Enumerate the elementary circuits inside each SCC, finding each cycle exactly once via the minimum-start-node rule.
- 4. Score every edge by how many cycles it sits in; recommend cutting the highest-scoring one per cycle.
- 5. Greedily build a minimal cut-set: repeatedly remove the edge in the most remaining cycles until none are left.
How to detect & break a cycle in 5 steps
Paste your imports or a dependency graph
Paste source files (separated by // file: markers), a madge --json export, a Graphviz DOT graph, or a simple edge list like a -> b. Source imports are parsed for JavaScript, TypeScript, Python, Java, C# and Go.
Pick the input language (or leave on auto)
Auto-detects the format and language from the text. Override the language if you're pasting raw source and want a specific parser, especially for relative-path resolution in JS/Python.
Detect the cycles
Click Detect. The import graph is built in your browser and Tarjan's strongly-connected-components algorithm finds every cycle, with each elementary circuit enumerated and the exact path traced — A → B → C → A.
Read the arc diagram and the acyclicity score
The arc diagram draws your modules on a ring with cyclic edges highlighted in red. The acyclicity score grades how much of the graph is tangle-free, and the cycle list shows every circuit found.
Cut the recommended edge and re-run
Each cycle names the single import to remove to break it — the highest-leverage edge, the one in the most cycles. Apply the dependency-inversion fix, paste the new graph back, and watch the score climb to 100%.
Why circular dependencies are the bug that hides
In 2026, a team ships a refactor, every test passes, and three weeks later production throws "cannot access X before initialization" — from a file nobody touched. The cause is a circular import that had worked purely by the accident of module load order, until a single new import shifted that order. Circular dependencies are the bug class that hides: invisible in review, silent in most test runs, and detonating far from where they live. This tool exists to make them visible on demand, before they ship.
A circular dependency is simply a cycle in the directed graph of "module A imports module B." The formal name for a tangled region is a strongly connected component — a set of modules where every one can reach every other by following imports — and the canonical way to find them is Tarjan's algorithm, published by Robert Tarjan in 1972, which discovers all SCCs in a single linear-time depth-first traversal. It's the same algorithm running in your browser when you click Detect, and it's why the analysis stays instant even on graphs of thousands of modules.
Different languages punish cycles differently, and the variety is instructive. Go took the hardest line: an import cycle is a compile error, full stop, so Go programmers learn to invert dependencies by reflex. Python is the most treacherous — a cycle often imports fine until the order changes, then raises "ImportError: cannot import name … (most likely due to a circular import)". JavaScript and TypeScript hoist and partially-evaluate modules, so a cyclic import can yield undefined at the top level while working inside a function; barrel files (index.ts re-exports) are a notorious source. Java and C# compile cycles happily but then surprise you at runtime through static-initializer order or, in Spring, a BeanCurrentlyInCreationException.
Beyond the crashes, cycles quietly tax everything around them. A bundler can't tree-shake or cleanly code-split modules that depend on each other, so a cycle can drag unrelated code into the same chunk and inflate the bundle the user downloads — which is why the bundle analyzer and this tool answer two halves of the same architecture question. Cyclic modules also can't be unit-tested in isolation, because you can't load one without the other, so mocks break down and tests silently become integration tests. And refactoring an SCC is dangerous precisely because it has no safe edit order — there's no module you can change first.
The fix is almost always dependency inversion: find what the two coupled modules actually share, extract it into a third module that both import, and the arrows now point one way. Where the coupling is only about wiring, dependency injection or a lazy import breaks the cycle; where it's only about types, TypeScript's import typeis erased at runtime and removes the cycle for free. The hard part was never the fix — it was knowing which edge to cut, which is exactly what this tool computes: for each cycle it names the highest-leverage import to remove, and for the whole graph it builds a minimal cut-set, because one well-chosen cut often breaks many overlapping circuits at once.
The discipline that keeps a codebase acyclic is the same one that keeps it secure and lean: measure continuously and fail loudly. Run an acyclicity check in CI (madge --circular, dependency-cruiser, or eslint-plugin-import/no-cycle) so a new cycle can never merge unnoticed, and treat the acyclicity score the way you treat test coverage — a number that should only go up. This analyzer is the fast feedback loop for that discipline, and it pairs with the npm dependency analyzer and the vulnerability scanner to keep the whole dependency story — structure, weight, and safety — under control.
Trusted by Architects, Platform & Refactoring Teams
“The recommended-cut-edge is the feature that makes this more than a visualiser. We piped our madge JSON in, it found a seven-module tangle our CLI only reported as one big SCC, and told us the two imports to invert. The arc diagram went straight into the architecture review deck.”
“We had an intermittent 'partially initialized module' error that only hit in prod. Pasted the package in, saw the config→models→services→config cycle in seconds, and the tool pointed at the exact import to defer. Fixed a bug that had survived three code reviews.”
“I run our barrel-file exports through this before every release — circular imports through index.ts files are silent killers for tree-shaking. The acyclicity score is now a number in our CI dashboard. Clean, fast, and genuinely the nicest dependency-graph UI I've used in a browser.”
“Go won't even compile a cycle, but mapping a planned refactor ahead of time here saved a painful afternoon — pasted the intended import edges, saw the cycle I was about to create, and restructured the shared package first. JSON export fed our ADR. Would love direct repo import next.”
Love using our calculator?
Related tools
Related Articles
Dive deeper with our expert guides and tutorials related to Circular Dependency Detector
Cycle detection via Tarjan's strongly-connected-components algorithm · cut recommendations via a greedy feedback-arc heuristic · Last reviewed: 2026-06