Go — import cycle not allowed
Break Go import cycles by moving shared types into a third package or inverting the dependency direction.
Symptoms
- import cycle not allowed
- package A imports B imports A
- Tests compile but the main build fails
- Adding a type to one package creates a cycle
Possible causes
- Two packages import each other directly or transitively
- Shared types were placed in the wrong package
- A test file imports a package that imports the package under test
- Refactoring created a cycle between a domain and its adapter
Diagnosis steps
Read the cycle path in the error
go list -deps ./... 2>&1 | head -40Interpretation
The error prints the chain of imports forming the cycle, which shows exactly which edge to remove.
Draw the dependency direction
go mod graph | grep -i "module"Interpretation
The module graph shows edges, helping decide which package should not depend on the other.
Move shared types out
mkdir internal/model && git mv types.go internal/model/Interpretation
A third package that both sides import removes the mutual dependency.
Root cause
Two or more packages depend on each other, directly or transitively, so the compiler cannot determine a build order. Go forbids import cycles by design.
Fix
Extract shared types or interfaces into a package that neither side needs to depend on, or invert the dependency using interfaces so one package depends only on an abstraction.
Verify the fix
Build and test again and confirm no import cycle remains, and confirm the moved types are still used correctly by both packages.
Symptom walkthrough
The build fails with import cycle not allowed and prints the chain forming the loop, such as package a imports b imports a. Go resolves packages by dependency order, so a cycle has no valid order. It often appears after moving a type to a package that already depended on the original, or when a test in one package imports another that imports it.
Fix in practice
Extract the shared types or interfaces into a new package that both sides import, or invert the dependency so one side depends on an interface it defines and the other implements it. This keeps the dependency graph acyclic and the direction pointed at abstractions.
Verification
Rebuild and run the tests and confirm the cycle is gone, then confirm the moved or inverted types still behave correctly on both sides. A clean build with passing tests confirms the refactor preserved behavior.
Diagnostic commands
Check the behavior with commands like these:
go list -deps ./... 2>&1 | head -40
go mod graph | grep -i "module"
mkdir internal/model && git mv types.go internal/model/