Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Go  Module 5 – Structs, pointers and methods

Embedding: composition instead of inheritance

Embed Go structs and interfaces to reuse fields and methods, see how promotion and shadowing pick a name, and why promoted methods never call the outer type's.

  • Intermediate
  • 15 minutes
  • Examples run with Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 6 of 15 minutes free
Free: An embedded field lends the outer type its fields and methods · The shallowest name wins, and a tie is an error only when used · A promoted method runs on the inner value, so nothing is overridden. With a pass: 2 in-depth parts and 3 interview answers, about 9 minutes.

What you will learn

  • Use an embedded struct and its promoted fields and methods
  • Predict which field or method a selector picks, and when it is ambiguous
  • Explain why a promoted method never calls the outer type's methods
  • Combine types by embedding interfaces in structs and in interfaces
  • Rewrite a class hierarchy as parts and an interface

Before you start

What you’ll learn · Before you start

What you will learn

  • Use an embedded struct and its promoted fields and methods
  • Predict which field or method a selector picks, and when it is ambiguous
  • Explain why a promoted method never calls the outer type's methods
  • Combine types by embedding interfaces in structs and in interfaces
  • Rewrite a class hierarchy as parts and an interface

Before you start

On this page

A Service that embeds a Logger can call svc.Log("starting") without declaring a single method, and a Java developer reading it sees a subclass. It is not one: when a method of the Logger calls another method, it calls the Logger’s own version, even if Service declares one with the same name.

You will embed a struct and use what it promotes, predict which name a selector finds, and see why nothing is ever overridden. Under each program is the output Go 1.27.1 printed when it ran.

An embedded field lends the outer type its fields and methods

type Service struct { Logger; Name string } has two fields. Logger is an embedded field: A struct field declared with a type and no name, such as Audit in struct { Audit; ID string }. The type's name serves as the field's name, and its fields and methods are promoted to the outer struct.: declared with a type and no name, it is called after its type, and its fields and methods are promoted field: A field or method of an embedded field that can be used as if the outer struct declared it, such as o.CreatedBy for o.Audit.CreatedBy, unless a name at a shallower depth hides it., usable as if Service had declared them:

service.go: a Service with an embedded Logger Go · service.go
// service.go: a Service embeds a Logger and gains its field and its method.
package main

import "fmt"

type Logger struct {
	Prefix string
}

func (l *Logger) Log(msg string) {
	fmt.Println(l.Prefix, msg)
}

type Service struct {
	Logger // embedded: Prefix and Log are promoted to Service
	Name   string
}

// LogWriter is any type that has a Log method.
type LogWriter interface {
	Log(msg string)
}

func main() {
	svc := &Service{Logger: Logger{Prefix: "[pay]"}, Name: "payments"}
	svc.Log("starting " + svc.Name) // promoted: svc.Logger.Log
	svc.Prefix = "[pay v2]"         // a promoted field
	svc.Logger.Log("the long way round")

	var w LogWriter = svc // *Service has Log, through its embedded Logger
	w.Log("through an interface")
	fmt.Printf("%+v\n", *svc)
}

Output

[pay] starting payments
[pay v2] the long way round
[pay v2] through an interface
{Logger:{Prefix:[pay v2]} Name:payments}

Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run service.go

svc.Log and svc.Prefix worked although Service declares neither, and svc.Logger.Log reached the same method by its full name. Step through how Go finds them:

Interactive · step by step How svc.Log finds a method Service never declared Step through the lookup of svc.Log in service.go, one depth at a time.
How svc.Log finds a method Service never declared, step 5 of 5. Showing Service, Name, Logger, embedded (Prefix is [pay v2]), Log, a method. In focus: Service, Log, a method.declaresembedshasServiceName, and Logger embeddedNamea field, depth 0Logger, embeddedPrefix is [pay v2]Log, a methodreceiver *Logger

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. Depth 0. For svc.Log, Go first looks at what Service declares itself, the field Name and the embedded field Logger. Neither is called Log.
  2. Depth 1. One level down, inside the embedded Logger, it finds Log, a method of *Logger. So svc.Log is short for svc.Logger.Log.
  3. The receiver is the inner value. The call runs Log with the receiver &svc.Logger, not svc. Log sees a Logger and its Prefix, and knows nothing about Service.
  4. A promoted field. svc.Prefix is found the same way, at depth 1, so svc.Prefix = "[pay v2]" writes the Prefix inside the embedded Logger.
  5. It counts for interfaces. Because *Service has Log through Logger, a *Service can be stored in a LogWriter variable, and w.Log runs Logger's method.

Go searched by depth: first what Service declares itself, then what its embedded Logger holds, one level down. The method ran with the inner value as its receiver, &svc.Logger, so Log never saw the Service around it. A promoted method also counts when Go checks an A type that lists method signatures, such as LogWriter with Log(msg string). Any type whose methods include them can be stored in a variable of that interface type, with nothing to declare.: *Service has Log through Logger, so a *Service could be stored in a LogWriter. An embedded field can also be an interface, whose methods call the value it holds, and an interface can embed others, as io.ReadWriter embeds io.Reader and io.Writer.

Checkpoint

Check: Embedded fields and promotion

  1. Question 1 of 2 With Logger and Service as in service.go, what does this print?

    Read the code, then choose one answer.

    svc := &Service{Logger: Logger{Prefix: "[a]"}, Name: "pay"}
    svc.Prefix = "[b]"
    fmt.Println(svc.Logger.Prefix)
    Show the answer: Question 1 of 2

    Answer: [b]

    service.go set svc.Prefix to [pay v2], and svc.Logger.Log printed with the new prefix.

  2. Question 2 of 2 When svc.Log("starting") runs the promoted method Log of the embedded Logger, what is Log's receiver?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: &svc.Logger, the Logger inside the Service

    svc.Log is short for svc.Logger.Log, and the receiver is the embedded value.

The shallowest name wins, and a tie is an error only when used

When the outer type declares a name that an embedded field also has, the outer one is found first, at depth 0, and hides the other. Two embedded fields at the same depth may both have a name:

shadow.go: one method shadowed, two Prefix fields Go · shadow.go
// shadow.go: the outer type's own method wins over a promoted one, and both embedded fields stay reachable.
package main

import "fmt"

type Logger struct{ Prefix string }

func (l Logger) Log(msg string) { fmt.Println(l.Prefix + msg) }

type Metrics struct{ Prefix string }

func (m Metrics) Count(name string) { fmt.Println(m.Prefix + "count " + name) }

type Service struct {
	Logger
	Metrics
}

// Log shadows Logger.Log, and still calls it by its full name.
func (s Service) Log(msg string) {
	s.Logger.Log("service: " + msg)
}

func main() {
	s := Service{Logger{"[log] "}, Metrics{"[metrics] "}}
	s.Log("ready")      // Service.Log, at depth 0
	s.Count("requests") // Metrics.Count, at depth 1
	fmt.Printf("%q %q\n", s.Logger.Prefix, s.Metrics.Prefix)
}

Output

[log] service: ready
[metrics] count requests
"[log] " "[metrics] "

Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run shadow.go

s.Log ran Service’s own Log, which called the hidden s.Logger.Log by its full name. s.Count came from Metrics. Logger and Metrics both have a Prefix, and that compiled because no line used s.Prefix; the program named each in full. Asking for the shared name is what the specification forbids:

ambiguous.go: s.Prefix with two candidates Go · ambiguous.go
// ambiguous.go: two embedded fields at the same depth both have a Prefix.
package main

import "fmt"

type Logger struct{ Prefix string }
type Metrics struct{ Prefix string }

type Service struct {
	Logger
	Metrics
}

func main() {
	s := Service{Logger{"[log] "}, Metrics{"[metrics] "}}
	fmt.Println(s.Prefix)
}

Output (exit status 1)

Printed as an error (standard error)

# command-line-arguments
./ambiguous.go:16:16: ambiguous selector s.Prefix

Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run ambiguous.go

ambiguous selector s.Prefix: there is not exactly one Prefix at the shallowest depth, so the compiler refuses to guess. A duplicate name at one level is harmless as long as nothing asks for it by its short name.

Checkpoint

Check: Shadowing and ambiguous names

  1. Question 1 of 2 Service embeds Logger, which has a field Prefix, and declares a field Prefix of its own. What does this print?

    Read the code, then choose one answer.

    s := Service{Logger{"[log]"}, "[svc]"}
    fmt.Println(s.Prefix, s.Logger.Prefix)
    Show the answer: Question 1 of 2

    Answer: [svc] [log]

    The shallowest name wins; a deeper one is hidden, never lost.

  2. Question 2 of 2 Both embeds X and Y, and both have a field ID. The program uses both.X.ID and both.Y.ID, never both.ID. Does it compile?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Yes; two names at one depth are an error only when the short name is used

    ambiguous.go failed only because it wrote s.Prefix.

A promoted method runs on the inner value, so nothing is overridden

Here is the surprise of the opening, as a program. Base’s Describe calls b.Name(), and Outer, which embeds Base, declares a Name of its own:

dispatch.go: Describe asks for a Name Go · dispatch.go
// dispatch.go: a method of an embedded type calls its own methods, never the outer type's.
package main

import "fmt"

type Base struct{}

func (Base) Name() string { return "base" }

func (b Base) Describe() string { return "I am " + b.Name() }

type Outer struct {
	Base
}

func (Outer) Name() string { return "outer" }

func main() {
	o := Outer{}
	fmt.Println(o.Name())     // Outer's own method
	fmt.Println(o.Describe()) // promoted from Base: calls Base.Name
}

Output

outer
I am base

Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run dispatch.go

o.Name() returned outer, but o.Describe() printed I am base. Follow the second call:

Interactive · step by step Why o.Describe() says base Step through the two calls of dispatch.go and watch which Name each one reaches.
Why o.Describe() says base, step 4 of 4. Showing Outer, Outer.Name, Base, embedded, Base.Name. In focus: Base, embedded, Base.Name.declaresembedsDescribe calls b.Name()Outerembeds BaseOuter.Namereturns "outer"Base, embeddedDescribe and NameBase.Namereturns "base"

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. o.Name(). Outer declares Name itself, at depth 0, so o.Name() runs Outer.Name and returns outer. Base's Name, one level down, is hidden.
  2. o.Describe(). Outer has no Describe, so Go finds Base's at depth 1 and calls it with the receiver o.Base, a Base value.
  3. Inside Describe. Describe calls b.Name(), and b is a Base. The compiler resolved that call on type Base when it compiled Describe, so it runs Base.Name.
  4. No overriding. Describe returns I am base. Nothing in it knows that an Outer exists; in Go only a call through an interface picks its method at run time.

Describe is Base’s method, compiled with a Base receiver, so the b.Name() inside it always means Base’s Name. In Java, a call like it inside a superclass reaches the subclass’s override; in Go, the FAQ says, methods on a concrete type are always resolved statically, and the only way to get dynamic dispatch: Choosing which method a call runs from the type of the value at run time, not from the type written in the code. In Go only calls through an interface are dispatched this way. is through an interface.

Key idea

Embedding lends a type the fields and methods of another, but every promoted method still runs on the inner value and calls the inner type’s methods.

Code ported from a language with inheritance runs into this first:

Embedding a base type and overriding its methods

Embedding Base and declaring Name on Outer shadows Name for code that calls o.Name(), and for no one else: Base’s own methods still call Base’s Name. Pass the behaviour in through an interface instead.

Checkpoint

Check: Promoted methods are never overridden

  1. Question 1 of 2 What does this print?

    Read the code, then choose one answer.

    type Base struct{}
    
    func (Base) Kind() string    { return "base" }
    func (b Base) Label() string { return "[" + b.Kind() + "]" }
    
    type Box struct{ Base }
    
    func (Box) Kind() string { return "box" }
    
    b := Box{}
    fmt.Println(b.Kind(), b.Label())
    Show the answer: Question 1 of 2

    Answer: box [base]

    dispatch.go printed outer and I am base for the same reason.

  2. Question 2 of 2 Shared code must call a Name method that each type supplies, the way an overridden method would. What does Go use?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: An interface that the shared code takes as a parameter or field

    Methods on concrete types are resolved statically, so the shared code takes an interface and each value passed in brings its own Name, as the lesson's mistake callout says: pass the behaviour in through an interface.

Embedding interfaces, and the API an embedded field exposes Pro: opens with a Pro, Premium or Ultimate pass

A struct can embed an interface as well as a struct, an interface can embed other interfaces, and whatever a type embeds becomes part of what its users can call. This part runs each case, including the nil panic of an empty embedded interface and a lock that leaks into a type’s API.

In this part:

  • A struct that embeds an interface hands calls to the value it holds
  • An interface that embeds interfaces asks for all their methods
  • Embedding a pointer: one part shared, or none at all
  • Embedding exports the embedded type’s methods

4 runnable examples (Go) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

From a class hierarchy to composition, case by case Pro: opens with a Pro, Premium or Ultimate pass

Code ported from Java or C# often arrives as a chain of embedded types that is meant to work like inheritance. This part shows where such a chain breaks, rebuilds it from parts and an interface, and weighs the four ways to give a type a part it needs.

In this part:

  • The hierarchy that never overrides
  • Parts and an interface instead
  • Getting dispatch back on purpose
  • A test fake that embeds the interface it stands in for
  • Four ways to give a type a part

4 runnable examples (Go) · about 7 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Practise

The practice questions stay with promotion, shadowing and the missing override, and come back to whichever of the three your answers show is weakest; each choice explains itself:

Practice that adapts to you

Practise this lesson

8 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 8 A struct embeds *log.Logger. What is the name of that field?

    Choose one answer.

    Show the answer: Question 1 of 8

    Answer: Logger

    Effective Go's Job struct reaches its embedded *log.Logger as job.Logger.

  2. Question 2 of 8 Service embeds Logger, whose Log method has a pointer receiver. Can a *Service be stored in a LogWriter?

    Choose one answer.

    Show the answer: Question 2 of 8

    Answer: Yes; *Service has Log, promoted from the embedded Logger

    Embedding lends the outer type the methods it needs to satisfy an interface.

  3. Question 3 of 8 Service embeds Logger and also declares its own Log. Which call still runs Logger's Log?

    Choose one answer.

    Show the answer: Question 3 of 8

    Answer: svc.Logger.Log("x")

    A shadowed method stays reachable through the embedded field's own name.

  4. Question 4 of 8 What does fmt.Println(a.ID) print?

    Read the code, then choose one answer.

    type C struct{ ID string }
    type B struct {
    	C
    	ID string
    }
    type A struct{ B }
    
    a := A{B{C{"c"}, "b"}}
    Show the answer: Question 4 of 8

    Answer: b

    Depth counts embedded fields traversed: B's ID needs one, C's needs two.

  5. Question 5 of 8 When is a name that two embedded fields share at the same depth an error?

    Choose one answer.

    Show the answer: Question 5 of 8

    Answer: When code uses the short name, such as s.Prefix

    Use the full names, s.Logger.Prefix and s.Metrics.Prefix, or declare the name on the outer type.

  6. Question 6 of 8 What does Go use where Java uses an overridable method of a superclass?

    Choose one answer.

    Show the answer: Question 6 of 8

    Answer: An interface, which chooses the method by the value's type at run time

    Methods on concrete types are always resolved statically; interfaces dispatch.

  7. Question 7 of 8 What does this print?

    Read the code, then choose one answer.

    type Animal struct{}
    
    func (Animal) Sound() string   { return "..." }
    func (a Animal) Speak() string { return "says " + a.Sound() }
    
    type Dog struct{ Animal }
    
    func (Dog) Sound() string { return "woof" }
    
    fmt.Println(Dog{}.Speak())
    Show the answer: Question 7 of 8

    Answer: says ...

    A promoted method runs on the inner value and calls the inner type's methods.

  8. Question 8 of 8 How do you make Speak use Dog's Sound, without repeating Speak in every animal type?

    Choose one answer.

    Show the answer: Question 8 of 8

    Answer: Make Speak a function that takes an interface with Sound: func Speak(s Sounder) string

    Methods on concrete types are resolved statically, so pass the behaviour that varies in through an interface, as the mistake callout says; Speak(Dog{}) would then return says woof.

Interview questions

Warm-up (fresher): Is embedding in Go the same as inheritance?

No. Embedding puts a value of one type inside another and promotes its fields and methods, so the outer type can use them by their short names. But a promoted method still runs on the inner value: when it calls another method, it calls the inner type’s own version, never one the outer type declares. Only a call through an interface chooses its method by the value’s type when the program runs.

Three longer questions of the module bank build on this lesson:

  1. Mid to senior

    A team ports a Java report generator to Go. BaseReport has a Render method that calls r.Header(), and SalesReport embeds BaseReport and declares its own Header. Every sales report renders the base header. Explain why, and redesign it the Go way.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A shared Cache type embeds sync.Mutex so that its methods can call c.Lock(). Months later a production deadlock is traced to a caller in another package that called Lock on a Cache and never released it. What made that possible, and how do you redesign the type?

    Model answer, Pro. First step and follow-ups

  3. Mid to senior

    A middleware wraps http.ResponseWriter in a struct that embeds it and counts bytes in its own Write. In one code path the wrapper is built with a nil writer and the handler panics, and streaming endpoints behind the middleware stop flushing. Explain both problems and how you build the wrapper safely.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • An embedded field is declared by its type alone, and its fields and methods are promoted to the outer type, which can then satisfy interfaces through them.
  • A selector finds the name at the shallowest depth; the outer type’s own names hide promoted ones, and two names at the same depth are an error only when used.
  • A promoted method runs on the inner value and calls the inner type’s methods, so nothing is overridden; only calls through an interface are dispatched at run time.

Terms in this lesson

dynamic dispatch
Choosing which method a call runs from the type of the value at run time, not from the type written in the code. In Go only calls through an interface are dispatched this way.
embedded field
A struct field declared with a type and no name, such as Audit in struct { Audit; ID string }. The type's name serves as the field's name, and its fields and methods are promoted to the outer struct.
promoted field
A field or method of an embedded field that can be used as if the outer struct declared it, such as o.CreatedBy for o.Audit.CreatedBy, unless a name at a shallower depth hides it.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.