Skip to main content

Composition

What is Composition?​

Composition is the practice of building complex types or behavior by combining simpler, smaller parts, instead of relying on class inheritance (which Go does not have). Composition is a "has-a" instead of "is-a".

  • Inheritance (not in Go): A Car is-a Vehicle.
  • Composition (Go way): A Car has-a Engine, has-a Wheels.

Composition in Go​

  • Simplicity: No inheritance chains to follow.
  • Flexibility: Swap parts easily (e.g., replace Engine with ElectricMotor).
  • Reusability: Components are loosely coupled and testable in isolation.
  • Idiomatic Go: Encourages small, composable building blocks (e.g., io.Reader, io.Writer).

Struct fields (explicit composition)​

type Engine struct {
HorsePower int
}

type Car struct {
Engine Engine // Car *has-a* Engine
Wheels int
}

func main() {
c := Car{Engine: Engine{HorsePower: 200}, Wheels: 4}
fmt.Println(c.Engine.HorsePower) // Access Engine through Car
}

Struct embedding (promoted fields & methods)​

Go lets you embed one struct inside another. The embedded struct's fields/methods are promoted, so you can call them directly.

type Logger struct{}

func (Logger) Info(msg string) {
fmt.Println("[INFO]", msg)
}

type Service struct {
Logger // embedded
Name string
}

func main() {
s := Service{Name: "Payments"}
s.Info("starting...") // calls Logger.Info
}
  • Service doesn’t inherit from Logger.
  • Instead, it has a Logger, and Go automatically promotes Logger's methods.

Interfaces + composition​

Behavior can be composed by depending on small interfaces.

type Reader interface {
Read(p []byte) (int, error)
}

type Writer interface {
Write(p []byte) (int, error)
}

// Compose both into a new interface
type ReadWriter interface {
Reader
Writer
}

Any type that implements both Read and Write automatically implements ReadWriter.

Real-world example​

github.com/ygrebnov/config.Provider[T]: