Start learning free

LESSON 187 OF 338 · OBJECT-ORIENTED PROGRAMMING

Method Overriding in Go: Shadowing Promoted Methods

Lesson Read the full explanation

TL ; DR:

When a child class defines a method with the same name as the parent's, the child's version overrides the parent's. Different objects can give the same method name different behavior.


type Dog struct{}

func (d Dog) bark() { fmt.Println("Woof!") }

type Puppy struct{ Dog }

func (p Puppy) bark() { fmt.Println("Yip!") }


Full lesson:

You've seen that a child class inherits the parent's methods. Overriding is what happens when the child wants to change one of those inherited methods.

Why override?

Subclasses are usually special cases of the parent. A Puppy is a Dog, but it sounds different — "Yip!" instead of "Woof!". Instead of inventing a new method name, you redefine bark() on the Puppy. Callers don't need to know which subclass they're holding — they just call bark() and the right version runs.

How it works in each language

  • C# — the parent method must be marked virtual, the child marks its replacement override. The compiler enforces this pairing.
  • Java — every non-final method is virtual by default; the @Override annotation is optional but tells the compiler to verify the signature matches.
  • Python — defining a method with the same name in the child class replaces the parent's. No keyword required.
  • JavaScript — same as Python: define the method again on the child class and it wins.
  • C++ — the parent method must be virtual; override on the child is optional but compiler-checked.

Your task

The Dog class already defines bark. Inside the Puppy subclass, override bark so it prints "Yip! I am " followed by the puppy's name. The harness creates a Puppy named "Buddy" and calls bark() on it.

Expected output:

Yip! I am Buddy
STEP 1 OF 2
    Ctrl + Enter to check

    GO · OBJECT-ORIENTED PROGRAMMING

    Method Overriding in Go, explained

    Replacing an inherited behaviour

    A Puppy is a kind of Dog, but it sounds different: "Yip!" instead of "Woof!". When a child type needs to change a method it got from its parent, other languages call this overriding. Go has no override keyword and no virtual keyword. What it has is shadowing: if the outer type declares a method with the same name as one promoted from an embedded type, the outer method wins.

    type Dog struct{}
    
    func (d Dog) bark() { fmt.Println("Woof!") }
    
    type Puppy struct{ Dog }
    
    func (p Puppy) bark() { fmt.Println("Yip!") }
    

    Now Puppy{}.bark() prints Yip!. Go looks for bark on Puppy first, finds it there, and never reaches the promoted one.

    The parent's method is still there

    Shadowing hides the embedded method behind a longer name; it does not delete it. The full path reaches the original, which is how a child extends a parent method instead of replacing it:

    func (p Puppy) bark() {
        p.Dog.bark() // run Dog's version first
        fmt.Println("...and a yip!")
    }
    

    This takes the place of the super.bark() call known from other languages.

    A real difference: no dynamic dispatch through the parent

    In languages with true overriding, a method defined in the parent that calls bark() runs the child's version when the object is really a child. Embedding does not work that way. The embedded Dog knows nothing about the Puppy around it, so its methods always call Dog's own methods. This program confirms it:

    type Dog struct{ name string }
    
    func (this Dog) bark()  { fmt.Printf("Woof! I am %s\n", this.name) }
    func (d Dog) intro()    { d.bark() }
    
    type Puppy struct{ Dog }
    
    func (this Puppy) bark() { fmt.Printf("Yip! I am %s\n", this.name) }
    
    func main() {
        p := Puppy{Dog: Dog{name: "Buddy"}}
        p.bark()      // Yip! I am Buddy
        p.Dog.bark()  // Woof! I am Buddy
        p.intro()     // Woof! I am Buddy
    }
    

    The last line shows it: intro is promoted from Dog and calls d.bark() on the embedded dog, so it prints the Dog version even when called through a puppy. When you need the child's behaviour to be chosen at run time, use an interface, as the next lesson does.

    Fields shadow too

    The same rule applies to fields. If Puppy declared its own field called name, then p.name would mean the puppy's field, and the dog's would be reachable only as p.Dog.name. The shallower name always wins, so the depth at which a name is declared decides which one a selector finds.

    Signatures need not match

    The outer method only has to share the name. It can take different parameters or return other types, because it simply hides the promoted one. Nothing checks that the two agree, so a typo in the name quietly creates a new method instead of replacing the old one. Compare this with the compiler-checked override markers of other languages.

    The lesson's exercise

    The starter code has Dog with bark, Puppy embedding it, a NewPuppy constructor and a mainFunction that creates "Buddy" and calls bark(). Only the new method is missing.

    1. Declare bark on Puppy, below NewPuppy, with the receiver (this Puppy). It shadows the method promoted from Dog.
    2. Print the message with fmt.Printf: Yip! I am and the name, using %s and a trailing \n. Here this.name reaches the embedded dog's field directly.
    type Dog struct {
        name string
    }
    
    func (this Dog) bark() {
        fmt.Printf("Woof! I am %s\n", this.name)
    }
    
    type Puppy struct {
        Dog
    }
    
    func NewPuppy(name string) Puppy {
        return Puppy{Dog: Dog{name: name}}
    }
    
    func (this Puppy) bark() {
        fmt.Printf("Yip! I am %s\n", this.name)
    }
    
    
    
    func mainFunction() {
        buddy := NewPuppy("Buddy")
        buddy.bark()
    }
    

    It prints:

    Yip! I am Buddy
    

    Common mistakes

    • Misspelling the name. Bark and bark are different methods in Go, so func (this Puppy) Bark() does not shadow bark, and the call still prints the Woof line.
    • Using the wrong receiver. A method on Dog cannot replace the promoted one. The new method needs the Puppy receiver.
    • Expecting the parent to call the child's version. As shown above, methods promoted from Dog keep calling Dog's methods.
    • Calling the parent's method by accident from inside the override. Writing this.bark() inside Puppy's bark calls itself forever. Use this.Dog.bark() to reach the original.

    Key takeaways

    • Declare a method with the same name on the outer type to replace a promoted one.
    • The original stays reachable through the embedded field, such as p.Dog.bark().
    • Embedded types never call back into the outer type's methods.
    • For run-time choice between behaviours, use interfaces.
    Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free