liveBook · Manning

Circular dependency

As explained in Go by Example

An import cycle is a circular dependency among Go packages that prevents the package import graph from being acyclic.

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
A screenshot of a computer

Description automatically generated

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

How Go by Example covers it

How deep it goes
Defines the error and works through layered package organization, while deferring interfaces and later service packages.
Examples
Uses mutually importing http and net packages, then a link core, link/rest API, linkd server, and alternative CLI tools.
What it assumes
Assumes familiarity with Go packages and imports while introducing layered dependency direction from concrete service structure.
What you can do after
Apply it by arranging focused Go packages so higher-level services import foundational packages without reverse dependencies.
How it connects
Connects package organization with layered design, REST APIs, HTTP servers, command-line tools, interfaces, and internal directories.

Also explained in

Compare how 5 books explain circular dependency →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Go by Example. The section links point to the parts of the book it draws on.