Skip to content
Tarjan SCC · elementary-cycle enumeration · in-browser

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

Every cycle, traced
Exact A → B → C → A paths
Recommended cut
The one import to remove per cycle
Arc diagram
See the tangle, not a wall of text
6 languages
JS/TS, Python, Java, C#, Go

Circular dependency console

·awaiting graph…

Why a circular dependency is worth breaking

A circular import can crash at load time, not compile time

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.

Cycles defeat tree-shaking and code-splitting

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.

Cyclic modules can't be unit-tested in isolation

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.

One cycle is rarely one edge

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

LanguageSymptomCauseFix
JavaScript / TSImported value is undefined at module initESM/CJS cycle: the importing module runs before the imported one finishes evaluatingMove the shared piece to a third module, or defer access (import inside the function, not at top level).
PythonImportError: cannot import name … (partially initialized module)Two modules import each other at top levelImport 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 declarationType-level cycle between modulesUse `import type` (erased at runtime) or hoist shared types into a types-only module.
JavaCompiles, but Spring throws BeanCurrentlyInCreationExceptionTwo beans constructor-inject each otherUse setter/field injection or @Lazy on one side; better, introduce a mediator that both depend on.
Goimport cycle not allowed (build fails outright)Go forbids package import cycles by designExtract the shared types/interfaces into a new package that both import, inverting the dependency.
C#Runtime null from a static initializerStatic constructors referencing each other across a cycleBreak the static coupling with an interface and dependency injection.
AnyBundle won't tree-shake / chunks merge unexpectedlyCyclic modules can't be separated by the bundlerCut the cycle; the bundler can then split the modules independently.
AnyRefactor 'touches everything'A strongly connected component has no safe edit orderMake 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.

ToolRunsInputLanguagesVisualization
This toolIn your browserImports, madge JSON, DOT, edge listJS/TS, Py, Java, C#, GoArc diagram + cut edges
madgeNode CLIYour source treeJS/TSGraphviz image
dependency-cruiserNode CLIYour source treeJS/TSGraph + rules
eslint-plugin-importLint stepSource, per-fileJS/TSLint errors only
Go vet / compilerBuild stepGo packagesGoBuild 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. 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. 2. Run Tarjan's algorithm to find every strongly-connected component in linear time. Any SCC larger than one node is a tangle.
  3. 3. Enumerate the elementary circuits inside each SCC, finding each cycle exactly once via the minimum-start-node rule.
  4. 4. Score every edge by how many cycles it sits in; recommend cutting the highest-scoring one per cycle.
  5. 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

1

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.

2

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.

3

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.

4

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.

5

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.

Circular Dependency FAQs

Have more questions? Contact us

Trusted by Architects, Platform & Refactoring Teams

4.8
Based on 4,820 reviews

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.

E
Elena Vasquez
Software architect, platform team
April 25, 2026

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.

R
Rahul Mehta
Senior Python engineer
February 14, 2026

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.

S
Sofia Lindgren
Frontend lead, design system
March 20, 2026

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.

D
Daniel Brooks
Staff engineer, Go microservices
December 9, 2025

Love using our calculator?

Related tools

Learn More

Related Articles

Dive deeper with our expert guides and tutorials related to Circular Dependency Detector

Loading articles...

Cycle detection via Tarjan's strongly-connected-components algorithm · cut recommendations via a greedy feedback-arc heuristic · Last reviewed: 2026-06