<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Augusto Occhiuzzi</title>
    <description>The latest articles on DEV Community by Augusto Occhiuzzi (@august_occh_dev).</description>
    <link>https://dev.to/august_occh_dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4216147%2F9c08d8a1-b1bc-4e66-acf6-5e5f8e4289c5.jpg</url>
      <title>DEV Community: Augusto Occhiuzzi</title>
      <link>https://dev.to/august_occh_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/august_occh_dev"/>
    <language>en</language>
    <item>
      <title>How to Detect Circular Dependencies in Java, TypeScript, Python and Go</title>
      <dc:creator>Augusto Occhiuzzi</dc:creator>
      <pubDate>Thu, 29 Oct 2026 23:08:16 +0000</pubDate>
      <link>https://dev.to/august_occh_dev/how-to-detect-circular-dependencies-in-java-typescript-python-and-go-2ib5</link>
      <guid>https://dev.to/august_occh_dev/how-to-detect-circular-dependencies-in-java-typescript-python-and-go-2ib5</guid>
      <description>&lt;p&gt;A circular dependency is rarely written on purpose. It shows up one reasonable import at a time — a service that needs a helper, a helper that needs a constant from the service — until three classes can no longer be changed without the other two. This guide covers how to find them in Java, TypeScript, Python and Go, how to break them, and how to stop new ones in code review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why they matter
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Changes ripple.&lt;/strong&gt; Every class in the cycle can be affected by a change to any other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing gets heavy.&lt;/strong&gt; To test one piece you have to construct the whole cycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime bugs.&lt;/strong&gt; In Node.js a circular &lt;code&gt;require&lt;/code&gt; or import can hand you a module that hasn't finished initialising, so a value is &lt;code&gt;undefined&lt;/code&gt; at load time. Spring Boot has refused circular bean references by default since 2.6.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Splitting becomes impossible.&lt;/strong&gt; A cycle can't be extracted into separate services or packages without breaking it first.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Java
&lt;/h2&gt;

&lt;p&gt;ArchUnit lets you write the rule as a unit test, failing the build when packages form a cycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;slices&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;matching&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.myapp.(*).."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;should&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;beFreeOfCycles&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;importedClasses&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Spring, a startup failure about circular references between beans is the framework telling you the same thing at runtime. Resist re-enabling &lt;code&gt;spring.main.allow-circular-references&lt;/code&gt;; fix the cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript and JavaScript
&lt;/h2&gt;

&lt;p&gt;madge builds the import graph and lists cycles; eslint-plugin-import catches them while you edit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx madge &lt;span class="nt"&gt;--circular&lt;/span&gt; &lt;span class="nt"&gt;--extensions&lt;/span&gt; ts src/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;.eslintrc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"import/no-cycle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Python
&lt;/h2&gt;

&lt;p&gt;Pylint reports import cycles as &lt;code&gt;cyclic-import&lt;/code&gt; (R0401), and pydeps can draw the module graph with the cycles highlighted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pylint &lt;span class="nt"&gt;--disable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;all &lt;span class="nt"&gt;--enable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cyclic-import mypackage
pydeps mypackage &lt;span class="nt"&gt;--show-cycles&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moving an import inside a function makes the error go away but keeps the cycle; treat it as a temporary patch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go
&lt;/h2&gt;

&lt;p&gt;The compiler does the package-level work for you: an import cycle between packages is a build error. Cycles between types inside one package are still allowed, and a large package can hide a tangle of structs that all depend on each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to break a cycle
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Invert one dependency.&lt;/strong&gt; Have A depend on an interface it owns, and let B implement it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract the shared part.&lt;/strong&gt; If both sides use the same types or helpers, move them into a third module both depend on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use an event instead of a call.&lt;/strong&gt; When B only needs to know that something happened in A, A can publish an event rather than calling B.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merge them.&lt;/strong&gt; If two classes can't work without each other, they may really be one.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Stop new cycles in pull requests
&lt;/h2&gt;

&lt;p&gt;Language tools check one language and usually one level — packages or files. &lt;a href="https://archynt.com" rel="noopener noreferrer"&gt;Archynt&lt;/a&gt; builds a class-level graph for Java, TypeScript, Python and Go and finds every cycle in it. On a pull request it analyses both the base and the head commit and reports only what the PR changed: a new cycle of four or more classes fails the check, a smaller one warns. The same analysis flags god classes, single points of failure and dead-code candidates.&lt;/p&gt;

&lt;p&gt;For public repositories it's a free &lt;a href="https://archynt.com/docs/github-action" rel="noopener noreferrer"&gt;GitHub Action&lt;/a&gt; — no account and no token shared:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Archynt architecture scan&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pull_request&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;archynt-scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;archynt/pr-check-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;java&lt;/span&gt; &lt;span class="c1"&gt;# java | go | node | python&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Private repositories use &lt;a href="https://archynt.com/docs/pr-checks" rel="noopener noreferrer"&gt;PR checks&lt;/a&gt; through the GitHub App, and the &lt;a href="https://archynt.com/class-diagram-from-code" rel="noopener noreferrer"&gt;class diagram&lt;/a&gt; shows where each cycle sits in the codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is a circular dependency?&lt;/strong&gt;&lt;br&gt;
Two or more modules or classes that depend on each other, directly (A uses B and B uses A) or through a chain (A → B → C → A). None of them can be understood, tested or changed in isolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why are circular dependencies bad?&lt;/strong&gt;&lt;br&gt;
They couple pieces that should be independent: a change in one ripples through the whole cycle, tests need the entire cycle wired up, and in some runtimes they cause real bugs, such as half-initialised modules in Node.js or bean creation failures in Spring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Go allow circular imports?&lt;/strong&gt;&lt;br&gt;
No. The Go compiler rejects import cycles between packages with an "import cycle not allowed" error. Types inside the same package can still depend on each other in a cycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I fix a circular dependency?&lt;/strong&gt;&lt;br&gt;
Usually by inverting one dependency through an interface, extracting the shared part into a module both can depend on, or replacing a direct call with an event. Merging the two pieces is the right answer when they were never really separate.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://archynt.com/blog/detect-circular-dependencies" rel="noopener noreferrer"&gt;archynt.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>java</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
