Go forbids import cycles. How do you detect and break them?
Question 436MediumGo 1.22 to 1.25
The compiler rejects cycles (import cycle not allowed) because package initialization and compilation units must form a DAG. A cycle usually signals a design problem: two packages that are really one, or a dependency pointing the wrong way.
Ways to break them:
- Dependency inversion with consumer-side interfaces. The lower-level package defines the small interface it needs, and the higher-level package satisfies it implicitly.
- Extract shared types into a leaf package (for example
domainormodel) that both import. Avoid turning it into a dumping-groundutilpackage. - Merge the packages if they are really one unit.
- Wire dependencies in
main(composition root): pass funcs or interfaces in at construction time.
// package order (no import of package user)
type UserLookup interface {
Email(ctx context.Context, id int64) (string, error)
}
type Service struct{ users UserLookup }
// package user imports order freely; main wires:
// svc := order.Service{users: userRepo}
Tests: an external test package (package order_test) can import packages that import order, which breaks test-only cycles. Diagnose with go list -deps or go mod graph at the module level. Interviewers look for "accept interfaces, defined where they are consumed" instead of hacks such as moving code into main.
More on Modules, Packages & Tooling
- Q434What are the gotchas of go:embed patterns? Why is my .env or _redirects file missing?
- Q435What does this print? Explain Go's package initialization order.
- Q437A vulnerable dependency shows up in your build. How do you find out why it's there and upgrade it safely?
- Q438What is Profile-Guided Optimization (PGO) in Go and how do you use it in production?
- Q439How does Go ship behavior changes without breaking old code? Explain GODEBUG, the godebug directive and //go:debug. What does this print?
- Q440What are module graph pruning and lazy module loading (Go 1.17), and why did go.mod files get so much longer?