A circular dependency occurs when packages import one another: if package A imports package B and B imports A, Go reports a compile-time import-cycle error. Package organization has trade-offs, but this rule is absolute: Go never permits import cycles. section 8.1.1
Figure 8.2 An imaginary import cycle scenario in which packages import each other
The two-package loop makes the failure concrete: each package requires the other before either can be compiled. section 8.1.1
Prevent cycles by treating package dependencies as layers. Lower layers provide more general functionality; higher layers add specialized behavior. Imports travel in one direction only: a higher layer may import a lower layer, never the reverse. Although Go packages are flat rather than hierarchical, thinking in layers clarifies dependency direction. section 8.1.1
Use package boundaries to keep dependencies explicit and useful:
- Start small. A project can remain in one package until growth creates a genuine reason to split it. section 8.1.1 section 8.8.1
- Give each package a focused area of expertise instead of using generic catch-all names such as common, util, shared, or helper. section 8.1.1
- Add packages across domain boundaries as the application or team grows, rather than predicting an elaborate architecture up front. section 8.8.1
Layering a link service
A core link package manages links without depending on other application packages. A link/rest package imports link to expose an HTTP REST API, while the linkd command wires packages together to run the server. A separate CLI can import link directly and skip link/rest; an HTTP client CLI can instead talk to the server. Keeping these directions explicit prevents the core package from depending upward on its REST layer. section 8.1.2
Figure 8.4 A layered approach to organizing packages
The package layout shows specialization increasing upward while imports remain directed toward foundational packages below. section 8.1.2
Layering is a practical guardrail, not a reason to create abstractions prematurely. Prefer small, cohesive packages and simple concrete code when there is no immediate need for indirection. As needs emerge, Go supports evolving the structure and introducing interfaces later; avoiding package sprawl helps retain clarity while staying clear of cycles. section 8.1.2 section 8.8.1




