Back to blog

Saturday, August 15, 2026

Go Struct Embedding vs Composition: What's the Difference?

Go Struct Embedding vs Composition: What's the Difference?

Go Struct Embedding vs Composition: What's the Difference?

They are not opposites. Composition is the design: a type has another type. Embedding is the syntax that makes that inner type's fields and methods callable on the outer value without writing the field name. A named field is the same composition with no promotion. Go has no inheritance either way.

The two forms

type Address struct {
    City string
}

func (a Address) Full() string { return a.City }

// Composition, no promotion — you always write c.Addr.City
type CustomerNamed struct {
    Addr Address
    Name string
}

// Composition with embedding — c.City and c.Full() are legal
type CustomerEmbed struct {
    Address
    Name string
}
n := CustomerNamed{Addr: Address{City: "Oslo"}, Name: "Liv"}
fmt.Println(n.Addr.City) // only path

e := CustomerEmbed{Address: Address{City: "Oslo"}, Name: "Liv"}
fmt.Println(e.City)       // promoted
fmt.Println(e.Address.City) // still valid
fmt.Println(e.Full())     // promoted method

The compiler still stores an Address field. Its implicit name is the type name (Address). Embedding never removes the inner value.

Why this is not inheritance

A function that takes Address will not accept CustomerEmbed. There is no is-a. Promoted methods run with the inner value as the receiver. If Address.Full used a as this in a subclass sense, it would see CustomerEmbed. It does not. There is no super, no virtual dispatch through the embed.

If the outer type declares Full itself, that shadows the promoted method. Both exist. e.Full() hits the outer one; e.Address.Full() hits the inner. The inner method never calls the outer one. That is shadowing, not override.

Use a named field when promotion would lie: a Server that has a Logger should usually say s.log.Info(...), not pretend the server is a logger. Embed when the outer type should honestly offer the inner API — io.ReadCloser style wrappers, or a Customer that really is "a person plus an address you always want at the top level."