Go

How do you structure a large Go project (cmd/, internal/, pkg/ debate)? How should packages be named and split to avoid stutter and cycles?

Question 556MediumGo 1.22 to 1.25

A common layout: cmd/<binary>/main.go holds thin mains that only do wiring. internal/ holds most of the code; the compiler enforces that only code rooted at internal's parent can import it, so you can refactor freely. pkg/ is only a convention for "importable by others". Many Go team members consider it noise, and golang-standards/project-layout is not an official standard. Start flat. Add structure when you have several binaries or a public API.

myapp/
  go.mod
  cmd/api/main.go        // flag parsing, wiring, run
  cmd/worker/main.go
  internal/order/        // domain: types + service + interfaces it needs
  internal/postgres/     // adapter implementing order.Repository
  internal/httpapi/      // transport

Naming: short, lowercase, single-word nouns. Avoid util, common, helpers, models, because they attract unrelated code. Avoid stutter: the caller writes order.Service, not order.OrderService, and http.Server, not http.HTTPServer. Split by domain/responsibility, not by layer: packages named controllers/, services/ and models/ tend to create cycles.

Avoiding import cycles (a compile error in Go): (1) point dependencies inward, so domain packages import nothing from adapters; (2) define interfaces in the consuming package so the implementation depends on the consumer, not the reverse; (3) move shared types into a small leaf package; (4) merge two packages that always change together. Interviewer angle: package boundaries are API boundaries. Minimize exported identifiers and mention internal/ enforcement.

More on Go Idioms, Design Patterns & Language Design

All 16 Go Idioms, Design Patterns & Language Design questions