Struct Embedding & Composition

Reviewed & published by Brayan K

Embedding places one type inside another with no field name so its fields and methods are promoted onto the outer type — Go's way of reusing code through composition instead of inheritance.

Part of the free Go course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ Embedding a Struct (Promoted Fields)

To embed, list a type inside a struct with no field name. The embedded type's fields are then promoted: you reach u.City directly even though City belongs to the inner Address. The inner value is still there under its type name (u.Address.City), and reads and writes through either path touch the same data.

package main

import "fmt"

type Address struct {
    City string
    Zip  string
}

// Embedding: list Address with NO field name inside User.
type User struct {
    Name    string
    Address // embedded — its fields are PROMOTED onto User
}

func main() {
    u := User{
        Name:    "Ada",
        Address: Address{City: "London", Zip: "EC1"},
    }

    // Promoted fields: reach Address fields directly on the User.
    fmt.Println(u.Name, u.City, u.Zip) // Ada London EC1

    // The embedded value is still reachable by its type name.
    fmt.Println(u.Address.City) // London

    // Updating through the promoted name works too.
    u.City = "Oxford"
    fmt.Println(u.Address.City) // Oxford
}

2️⃣ Promoted Methods & Overriding

Methods are promoted too: a Dog that embeds Animal automatically has Animal's methods. If you define a method with the same name on the outer type, it overrides (shadows) the promoted one — Go picks the shallowest match. The inner method is still reachable explicitly via the type name (d.Animal.Speak()), which lets an override extend rather than fully replace the original.

package main

import "fmt"

type Animal struct {
    Name string
}

func (a Animal) Speak() string {
    return a.Name + " makes a sound"
}

func (a Animal) Describe() string {
    return "I am " + a.Name
}

// Dog embeds Animal, so it gets Speak and Describe for free...
type Dog struct {
    Animal
}

// ...but it OVERRIDES Speak with its own version.
func (d Dog) Speak() string {
    return d.Name + " barks"
}

func main() {
    d := Dog{Animal{Name: "Rex"}}

    fmt.Println(d.Speak())    // Rex barks      (override wins)
    fmt.Println(d.Describe()) // I am Rex       (promoted method)

    // The inner method is still reachable explicitly.
    fmt.Println(d.Animal.Speak()) // Rex makes a sound
}

3️⃣ Embedding an Interface

You can embed an interface as well, which is the idiomatic way to decorate behaviour. Embed the interface, override only the methods you want to change, and delegate the rest to the embedded value. Below, LoggedSorter embeds sort.Interface and counts swaps while passing the real work through to the wrapped sorter.

package main

import (
    "fmt"
    "sort"
)

// Embed an INTERFACE to wrap/decorate it. Here we add logging
// around any sort.Interface by embedding it.
type LoggedSorter struct {
    sort.Interface // embedded interface
    swaps          int
}

func (s *LoggedSorter) Swap(i, j int) {
    s.swaps++
    s.Interface.Swap(i, j) // delegate to the embedded implementation
}

func main() {
    data := []int{3, 1, 2}
    ls := &LoggedSorter{Interface: sort.IntSlice(data)}

    sort.Sort(ls)
    fmt.Println("sorted:", data)
    fmt.Println("swaps: ", ls.swaps)
}

🎯 Your Turn

Embed Engine in Car so you can read c.HP directly. Fill in the two blanks.

package main

import "fmt"

type Engine struct {
    HP int
}

type Car struct {
    ___          // 👉 Engine  (embed it — no field name)
    Brand string
}

func main() {
    c := Car{Engine: Engine{HP: 120}, Brand: "Tesla"}

    // Reach the promoted HP field directly on the Car.
    fmt.Println(c.Brand, "has", c.___, "HP") // 👉 HP

    // ✅ Expected output:  Tesla has 120 HP
}

Common Errors (and the fix)

🎯 Mini-Challenge: Override with Delegation

Build a Widget that embeds Base, adds a Label, and overrides Info() so it calls the embedded Base.Info() inside its own output.

package main

import "fmt"

type Base struct {
    ID int
}

func (b Base) Info() string {
    return fmt.Sprintf("Base#%d", b.ID)
}

// 🎯 MINI-CHALLENGE: build Widget by EMBEDDING Base.
// Give Widget a Label field and OVERRIDE Info() so it returns
//   Widget "OK" (Base#7)
// by calling the embedded Base.Info() inside your version.
//
// ✅ Expected output:  Widget "OK" (Base#7)

func main() {
    // w := Widget{Base: Base{ID: 7}, Label: "OK"}
    // fmt.Println(w.Info())
}

🎉 Lesson Complete!

Practice quiz

How do you embed a type in a struct?

  • Give it a field name like Addr Address
  • Use the embed keyword
  • List the type with no field name
  • Wrap it in a pointer always

Answer: List the type with no field name. Embedding lists a type with no field name; that promotes its fields and methods.

If User embeds Address (with a City field), how do you read City directly?

  • u.City
  • u.Address[City]
  • u.getCity()
  • City(u)

Answer: u.City. Promoted fields are reachable directly: u.City works as well as u.Address.City.

Does Go have class inheritance?

  • Yes, via embedding
  • Yes, with the extends keyword
  • Only for interfaces
  • No — embedding is composition, not inheritance

Answer: No — embedding is composition, not inheritance. Go has no inheritance; embedding is composition that promotes members.

How do you override a promoted method?

  • Use the override keyword
  • Define a method with the same name on the outer type
  • Delete the inner method
  • You cannot override it

Answer: Define a method with the same name on the outer type. Defining a same-named method on the outer type shadows the promoted one.

After Dog overrides Speak, how do you still call the embedded Animal's Speak?

  • d.Animal.Speak()
  • d.Speak()
  • Animal.Speak(d)
  • super.Speak()

Answer: d.Animal.Speak(). The inner method stays reachable explicitly via the type name: d.Animal.Speak().

How do you initialise an embedded field in a struct literal?

  • By a made-up field name
  • It is always zero-valued
  • By its type name, e.g. Address: Address{...}
  • With the embed: keyword

Answer: By its type name, e.g. Address: Address{...}. There is no separate name; you use the type name: User{Address: Address{...}}.

What happens if two embedded types both have a field named Name and you write v.Name?

  • Go picks the first one
  • It is an ambiguous selector — a compile error
  • It returns both
  • It panics at runtime

Answer: It is an ambiguous selector — a compile error. Direct use of an ambiguous promoted name is a compile error; qualify by type to resolve.

How do you resolve an ambiguous embedded name?

  • Rename the struct
  • Use a pointer
  • Remove one embed
  • Qualify it by type, e.g. v.TypeA.Name

Answer: Qualify it by type, e.g. v.TypeA.Name. Qualify with the type name (v.TypeA.Name) to disambiguate.

Why embed an interface (like sort.Interface) instead of a struct?

  • It is faster
  • To wrap/decorate an implementation, overriding some methods and delegating the rest
  • Interfaces cannot be fields otherwise
  • To avoid promotion

Answer: To wrap/decorate an implementation, overriding some methods and delegating the rest. Embedding an interface lets you decorate behaviour: override what you need, delegate the rest.

In LoggedSorter, how does the overriding Swap forward to the embedded implementation?

  • s.Swap(i, j)
  • super.Swap(i, j)
  • s.Interface.Swap(i, j)
  • sort.Swap(i, j)

Answer: s.Interface.Swap(i, j). It delegates to the embedded value via s.Interface.Swap(i, j).

Continue this course

Frequently asked questions

What is the difference between embedding and a normal field?

A normal field has a name: Addr Address. An embedded field has NO name — you just write the type: Address. Embedding promotes the inner type's fields and methods so you can access them directly on the outer struct (u.City instead of u.Addr.City). A named field keeps everything namespaced under that field.

Does Go have inheritance?

No. Go deliberately has no class inheritance. Embedding looks a little like inheritance because methods and fields are promoted, but it is really composition: the outer type contains an instance of the inner type and forwards to it. The Go proverb is 'composition over inheritance' — you build behaviour by assembling small pieces rather than extending a base class.

How do I override a promoted method?

Just define a method with the same name on the outer type. Because Go resolves the shallowest method first, the outer type's version 'wins' and shadows the embedded one. You can still reach the embedded version explicitly through the type name, for example d.Animal.Speak(), which is handy when your override wants to extend rather than replace the original.

What happens if two embedded types have the same field or method name?

Accessing that name directly becomes ambiguous and is a compile error. Go won't guess which one you meant. You resolve it by qualifying with the type name (for example v.TypeA.Name vs v.TypeB.Name), or by adding a method on the outer type that picks one explicitly. Ambiguity only triggers when you actually use the conflicting name.

Why would I embed an interface instead of a struct?

Embedding an interface lets you wrap or decorate any implementation of it while overriding just the methods you care about. The classic pattern is adding behaviour — logging, counting, validation — around an existing interface value: embed the interface, override one method to do your extra work, and delegate the rest to the embedded value. It is composition for behaviour rather than data.