Question
I am getting the Go error import cycle not allowed while trying to test my controller. This is the output:
can't load package: import cycle not allowed
package project/controllers/account
imports project/controllers/base
imports project/components/mux
imports project/controllers/account
import cycle not allowed
package project/controllers/account
imports project/controllers/base
imports project/components/mux
imports project/controllers/account
import cycle not allowed
package project/controllers/account
imports project/controllers/base
imports project/components/mux
imports project/controllers/routes
imports project/controllers/base
How should I read and understand this error message? Where is the dependency cycle, and how can I identify which import relationship is wrong?
Short Answer
By the end of this page, you will understand what an import cycle is in Go, how to read the chain shown in the error message, why Go rejects circular package dependencies, and how to refactor your packages to remove the cycle.
Concept
In Go, a package can import another package, but the full dependency graph must remain acyclic. That means you cannot have a chain of imports that eventually leads back to the starting package.
A simple example of a cycle is:
// package a imports b
// package b imports a
A longer cycle is also invalid:
// account -> base -> mux -> account
This is exactly what your error is showing.
Why Go disallows import cycles
Go packages are designed to compile cleanly and independently. Circular dependencies make code harder to:
- compile
- test
- understand
- reuse
- maintain
If package account depends on base, and base depends on something that eventually depends on account again, then those packages are tightly coupled. Go forces you to break that coupling.
How to read the error
The error shows the import path chain that leads to the cycle.
From your output:
package project/controllers/account
imports project/controllers/base
imports project/components/mux
imports project/controllers/account
This means:
project/controllers/accountimportsproject/controllers/base
Mental Model
Think of package imports like roads between cities, but traffic is only allowed to move forward.
accountcan go tobasebasecan go tomux- but if
muxgoes back toaccount, you have driven in a circle
Go does not allow circular road maps between packages.
Another way to think about it: package imports should form a tree or directed graph without loops, not a ring. Once one package depends on another, that dependency direction should stay one-way.
Syntax and Examples
In Go, imports happen at the package level.
Normal import
package account
import "project/controllers/base"
This is fine if base does not eventually import account again.
Invalid circular structure
// project/controllers/account/account.go
package account
import "project/controllers/base"
func HandleAccount() {
base.Render()
}
// project/controllers/base/base.go
package base
import "project/components/mux"
func Render() {
mux.Register()
}
// project/components/mux/mux.go
package mux
import "project/controllers/account"
func Register {
account.HandleAccount()
}
Step by Step Execution
Consider this simplified dependency chain:
// account imports base
// base imports mux
// mux imports account
What happens step by step
When Go tries to build or test account:
- It starts loading package
account - It sees that
accountimportsbase - It loads
base - It sees that
baseimportsmux - It loads
mux - It sees that
muximportsaccount - But
accountis already in the current import chain - Go detects a loop and stops with
import cycle not allowed
Trace of your error
From your message:
account -> base -> mux -> account
This is enough to fail.
And another trace:
Real World Use Cases
Import cycle errors often appear in real Go projects when package responsibilities become mixed.
Common situations
-
Controllers and routers importing each other
- A controller registers routes
- The router imports controllers to call handlers
- This easily creates a cycle
-
Base utilities depending on higher-level packages
- A
basepackage should usually provide common functionality - If it imports controller-specific code, it stops being a true base layer
- A
-
Testing setup
- Test files may import helper packages that import the package under test indirectly
- This can expose cycles that were already present in the project structure
-
Shared configuration or initialization code
- A package meant for shared setup sometimes imports feature packages
- Feature packages then import the shared setup package back
Typical fix direction
Move shared code into neutral packages such as:
internal/httpxinternal/renderinternal/routerpkg/configpkg/types
Real Codebase Usage
In real Go codebases, developers avoid import cycles by designing clear package layers.
Common package direction
A common structure is:
handlers/controllers -> services -> repositories -> database
Each layer depends only on lower layers, not the other way around.
Useful patterns
1. Dependency inversion with interfaces
Instead of mux importing account, define an interface or register handler functions without importing the controller package back.
package mux
type HandlerFunc func()
func Register(path string, h HandlerFunc) {
// store route and handler
}
package account
import "project/components/mux"
func Handle() {}
func Setup() {
mux.Register("/account", Handle)
}
Common Mistakes
1. Putting shared logic in a package that is not actually shared
A package named base or common often grows too much and starts importing feature packages.
Broken idea:
package base
import "project/controllers/account"
If account already imports base, you now have a cycle.
Avoid it: keep base independent of feature packages.
2. Router and controller importing each other
Broken pattern:
// mux imports account
// account imports mux
Avoid it: let a top-level package connect them, or register handlers through function parameters.
3. Assuming the first package listed is the only problem
The error shows a chain, not just one bad package. The issue is the loop formed by all packages together.
Avoid it: inspect every package in the chain.
4. Fixing only one file without fixing package design
Even if you remove one import, another cycle may remain.
Your error already shows multiple cycles.
Comparisons
| Concept | What it means | Allowed in Go? | Example |
|---|---|---|---|
| One-way import | One package depends on another without looping back | Yes | account -> base |
| Direct cycle | Two packages import each other | No | a -> b -> a |
| Indirect cycle | A longer chain returns to the starting package | No | a -> b -> c -> a |
| Top-level composition | main wires packages together without lower packages importing upward | Yes | main -> mux, main -> account |
| Shared utility package |
Cheat Sheet
How to read the error
If you see:
package A
imports B
imports C
imports A
that means:
A -> B -> C -> A
Cycle found.
Rules
- Go packages cannot form circular imports
- Cycles can be direct or indirect
- The full import graph must be acyclic
- Tests can also reveal cycles
Quick debugging steps
- Start from the first package in the error
- Follow each
importsline in order - Stop when the path returns to a package already seen
- Inspect those packages' import statements
- Refactor so dependencies flow one way
Common fixes
- Move shared code into a neutral package
- Use
mainto wire dependencies together - Replace package knowledge with interfaces or function arguments
- Remove imports from low-level packages to high-level packages
- Merge packages if they are inseparable
Your specific cycles
account -> base -> mux -> account
base -> mux -> routes -> base
Good dependency direction
controllers -> services -> repositories
controllers -> shared/http
main -> controllers + router
FAQ
What does import cycle not allowed mean in Go?
It means your packages form a circular dependency. One package imports another package that eventually imports the first package again.
How do I find the cycle in the error message?
Read the packages in order from top to bottom. The cycle exists when the chain returns to a package already listed earlier.
Is the wrong dependency always the last import shown?
Not necessarily. The problem is the whole loop, not just one line. Any import in the cycle may need to be changed depending on package design.
Why does Go forbid circular imports?
They make code harder to compile, reason about, test, and maintain. Go encourages clear one-way package dependencies.
Can test files cause import cycles?
Yes. Test helpers or external test packages can introduce dependency paths that expose cycles.
Should I use interfaces to fix import cycles?
Sometimes, yes. If one package only needs behavior, using interfaces or callback functions can remove the need for a direct import.
Should I merge packages to fix a cycle?
Sometimes. If two packages are always used together and cannot be separated cleanly, merging them may be simpler than forcing an artificial boundary.
Mini Project
Description
Build a small Go application structure with controllers and routing, then organize it so route registration does not create an import cycle. This demonstrates how to keep dependencies flowing in one direction.
Goal
Create a simple account route registration setup in Go without any circular package imports.
Requirements
- Create a
muxpackage that can register a route and a handler function. - Create an
accountpackage with a handler function. - Avoid making
muximportaccount. - Use a top-level package such as
mainto connect the router and controller. - Print the registered route to prove the setup works.
Keep learning
Related questions
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.
Blank Identifier Imports in Go: What `_` Means in an Import Statement
Learn what `_` means in a Go import, why blank identifier imports run package init code, and when to use them safely.
Calling Functions Across Files in the Same Go Package
Learn how Go uses packages across multiple files, why functions may appear undefined, and how to organize code correctly.