What is struct embedding, and how does it enable composition over inheritance?

5 minintermediategoembeddingcompositionstructs

Quick Answer

Struct embedding places one type inside another without naming a field for it: type Manager struct { Employee; Reports []Employee }. The embedded type's fields and methods get promoted to the outer struct, so m.Name works directly if Employee has a Name field, without writing m.Employee.Name. This is Go's mechanism for code reuse and "is-a"-like behavior without classical inheritance — there's no subclassing, no virtual dispatch, and no super call. If the outer struct defines a method with the same name as a promoted one, the outer struct's method wins, which is how you override embedded behavior. Interfaces can be embedded the same way, letting you compose a larger interface out of smaller ones (as io.ReadWriter does with io.Reader and io.Writer).

Detailed Answer

Go has no class inheritance at all. Embedding is how it gets most of the code-reuse benefit without the complexity that comes with it.

Struct embedding and field/method promotion

type Employee struct {
    Name string
    ID   int
}

func (e Employee) Describe() string {
    return fmt.Sprintf("%s (#%d)", e.Name, e.ID)
}

type Manager struct {
    Employee        // embedded, no field name
    Reports []Employee
}

m := Manager{Employee: Employee{Name: "Alice", ID: 1}}
fmt.Println(m.Name)        // "Alice" — promoted field, no m.Employee.Name needed
fmt.Println(m.Describe())  // promoted method, works directly on Manager

Overriding a promoted method

func (m Manager) Describe() string {
    return fmt.Sprintf("Manager: %s", m.Employee.Describe())
}

fmt.Println(m.Describe())  // uses Manager's own Describe, not Employee's

Defining a method with the same name on the outer type shadows the embedded one. Note there's no super.Describe() equivalent — the overriding method must call m.Employee.Describe() explicitly if it wants the embedded behavior too.

Interface embedding

type Reader interface {
    Read(p []byte) (n int, err error)
}
type Writer interface {
    Write(p []byte) (n int, err error)
}
type ReadWriter interface {
    Reader   // embeds Reader's method set
    Writer   // embeds Writer's method set
}

Any type implementing both Read and Write automatically satisfies ReadWriter, with no extra declaration.

Why "composition over inheritance" here

Embedding gives you code reuse and promoted behavior. But the embedded type has no idea it's embedded, there's no polymorphic dispatch through a base-class pointer, and the relationship stays explicit and visible in the struct definition. This avoids the classic fragile-base-class problems of deep inheritance hierarchies in other languages.

Related Resources