Skip to main content
Preslav Rachev
  1. My Writings /Programming /

Go: Import Cycles via Third Packages Are Not Permitted

·3 mins
You cannot trick the Go compiler, and that is a good thing.

This is a quick note for beginner Go developers. One of the first things people learn when starting to work with is Go is that import cycles are not allowed. If package a refers to package b, and b back to a, you will see the following error during compilation:

package command-line-arguments
        imports github.com/preslavrachev/hello/a
        imports github.com/preslavrachev/hello/b
        imports github.com/preslavrachev/hello/a: import cycle not allowed
graph TD A[Package A] -->|import| B[Package B] B -->|import not allowed| A

This restriction has been made by design. It is supposed to ease the compilation process, and give the project a single direction. If every package could depend on any other, and vice-versa, projects would easily turn into an incomprehensible mess.

graph TD A[Package A] -->|import| B[Package B] B -->|import| C[Package C] C --> |import| A C --> |import| B C -->|import| D[Package D] D -->|import| A D -->|import| C D -->|import| B A -->|import| C C -->|import| E[Package E] E -->|import| B E -->|import| A

In addition, it is one of the reasons for Go’s blazing fast compilation speed - with circular dependencies, the compiler would have to recompile everything every time something changed. Rob Pike, one of Go’s creators said as much in a PR comment back in 2019:

The lack of import cycles in Go forces programmers to think more about their dependencies and keep the dependency graph clean and builds fast. Conversely, allowing cycles enables laziness, poor dependency management, and slow builds. Eventually one ends up with a single cyclical blob enclosing the entire dependency graph and forcing it into a single build object. This is very bad for build performance and dependency resolution. These blobs are also take much more work to detangle than the is required to keep the graph a proper DAG in the first place.

You can’t trick the Go compiler #

Just in case people thought that they could trick the compiler, implicitly causing a circular dependency via a 3rd (or 4th) package import, I wanted to reassure everyone that is not possible, either.

package command-line-arguments
        imports github.com/preslavrachev/hello/a
        imports github.com/preslavrachev/hello/b
        imports github.com/preslavrachev/hello/c
        imports github.com/preslavrachev/hello/d
        imports github.com/preslavrachev/hello/a: import cycle not allowed
graph TD A[Package A] -->|import| B[Package B] B -->|import| C[Package C] C -->|import| D[Package D] D -->|import not allowed| A

In short, if you need to share some common functionality between multiple packages, your only option is to extract it into its own separate package. This ensures the single direction of the graph and builds what is called a Direct Acyclic Graph (DAG):

graph TD A[Package A] -->|import| B[Package B] B -->|import | C[Package C] A -->|import | C D[Package D] -->|import | C

Have something to say? Join the discussion below ๐Ÿ‘‡

Want to explore instead? Fly with the time capsule ๐Ÿ›ธ

You may also find these interesting

Consistent > Idiomatic

·2 mins

As a software engineer, I’ve learned that consistency in code is crucial for the long-term success of a project, even when it means deviating from idiomatic principles.