Method Promotion & Overriding
Embedding promotes fields — you saw that last lesson. It promotes methods too. If the embedded type has a method, the outer type appears to have it as well:
package main
import "fmt"
type Engine struct{}
func (e Engine) Start() string {
return "vroom"
}
type Car struct {
Engine // embedded
Brand string
}
func main() {
c := Car{Brand: "Fiat"}
fmt.Println(c.Start()) // vroom — Engine.Start, promoted onto Car
}
Car never declares Start, yet c.Start() works. The call is shorthand for
c.Engine.Start(). This is how you reuse behavior in Go: embed a type, get its
methods for free.
Overriding: the outer method wins
The outer type can declare a method with the same name as a promoted one. When it does, the outer method shadows the embedded one — Go picks the method at the shallowest depth:
type SportsCar struct {
Engine
}
func (s SportsCar) Start() string {
return "VROOM VROOM"
}
// SportsCar{}.Start() -> "VROOM VROOM", not "vroom"
Calling Start() on a SportsCar runs SportsCar.Start, because it sits one
level up from the promoted Engine.Start. People call this "overriding", but
it's really just shadowing by name and depth.
The embedded method is still callable
Overriding doesn't erase the inner method — you reach it by naming the embedded field, which is perfect for wrapping and extending:
func (s SportsCar) Start() string {
base := s.Engine.Start() // "vroom", the promoted method
return strings.ToUpper(base) + "!"
}
That pattern — call the embedded version, then add to it — is the closest Go
gets to a super.method() call, and it's fully explicit: you say exactly which
method you mean.
No virtual dispatch
Here's the part that trips up people arriving from Java or C++. Promotion is
resolved statically, at compile time, from the declared type. There is no
virtual dispatch. If a method on Engine calls Start(), it always calls
Engine.Start — embedding a SportsCar around it does not redirect that
inner call to SportsCar.Start. The inner type doesn't know it's embedded and
can't "call up" into the outer one.
Why design it this way? Because it keeps behavior local and predictable. A method does exactly what its source says, every time, with no action-at-a- distance from some subtype three layers away. When you want polymorphism in Go, you reach for interfaces (next lesson) — an explicit, deliberate mechanism — rather than getting it implicitly and surprisingly through a base class. Embedding is for reuse; interfaces are for substitutability. Keeping those two jobs separate is a large part of why Go code stays easy to follow.
Try it
You'll embed a Greeter and override its Greet method to shout — calling the
embedded version first, then transforming its result.
Practice
1. The loud greeter
IntermediateThe starter defines a basic greeter:
type Greeter struct{} func (g Greeter) Greet() string { return "hello" }LoudGreeteris already declared to embedGreeter. Override itsGreetso it returns the embedded greeting shouted — uppercased with a!on the end:func (lg LoudGreeter) Greet() stringRequirements:
LoudGreeter{}.Greet()must return"HELLO!".- Build it from the embedded method — call
lg.Greeter.Greet()to get"hello", then transform that. Don't hard-code the string. - The embedded
Greeteris untouched:Greeter{}.Greet()still returns"hello".
Use
strings.ToUpperfor the uppercasing.package main import "fmt" // Greeter returns a plain greeting. type Greeter struct{} func (g Greeter) Greet() string { return "hello" } // LoudGreeter embeds Greeter and overrides Greet to shout it. type LoudGreeter struct { Greeter } // Greet should return the embedded greeting uppercased with a "!" appended. func (lg LoudGreeter) Greet() string { // TODO: implement — strings.ToUpper(lg.Greeter.Greet()) + "!" return "" } func main() { fmt.Println(LoudGreeter{}.Greet()) }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.