Start learning free

LESSON 186 OF 338 · OBJECT-ORIENTED PROGRAMMING

Inheritance in Go: Struct Embedding

Lesson Read the full explanation

TL ; DR:

A child struct can embed a parent struct — it gets all the parent's fields and methods for free, and can add its own. This lets you reuse code instead of copy-pasting it.


type Puppy struct {
    Dog
}

func (p Puppy) whine() {
    fmt.Println("whimper")
}


Full lesson:

Inheritance is one of the four classic OOP pillars (along with encapsulation, polymorphism, and abstraction). The idea is simple: most real-world things are kinds of other things — a Puppy is a Dog, a Car is a Vehicle, a Manager is an Employee. When you model that "is-a" relationship in code, the child class gets the parent's data and behavior automatically.

What gets inherited?

  • Fields — the Puppy has a name field even though we only declared it on Dog.
  • Methods — the Puppy can call bark() even though we only defined it on Dog.
  • The contract — anywhere code expects a Dog, you can pass a Puppy (we'll see this when we cover polymorphism).

What you still write

  • The constructor usually has to forward arguments up to the parent's constructor (because the parent's fields need initializing).
  • Any new methods or new fields specific to the child.

Your task

Below the Dog struct, create a Puppy struct that embeds Dog and adds one new method, whine(), that prints "whimper". The harness creates a Puppy named "Buddy", calls its inherited bark(), and then its new whine().

Expected output:

Woof! I am Buddy
whimper
STEP 1 OF 8
    Ctrl + Enter to check

    GO · OBJECT-ORIENTED PROGRAMMING

    Inheritance in Go, explained

    Go has no inheritance, but it has embedding

    Inheritance models an "is-a" relationship: a Puppy is a kind of Dog, so it should get the dog's data and behaviour without copy-pasting. Go has no extends keyword and no class hierarchy. It reuses code by embedding: you put one struct type inside another without giving it a field name.

    type Puppy struct {
        Dog
    }
    

    The line Dog is an anonymous (embedded) field. Its field name is the type name, Dog, and everything in Dog is promoted to Puppy.

    What gets promoted

    • Fields. If Dog has a name field, a Puppy value can use p.name directly. The longer form p.Dog.name means the same thing.
    • Methods. If Dog has a bark method, you can call p.bark().

    What you still write yourself is the child's own extras: new fields, new methods, and a constructor that sets up the embedded part.

    Why reuse by embedding

    The point of inheritance is to write shared code once. With embedding, the shared name field and bark method exist only in Dog, and every type that embeds Dog gets them. If bark changes, all embedding types change with it. Because the relationship is a plain field, it is also visible in the code: you can read the struct and see exactly what it contains.

    Embedding is composition, not a subtype

    A Puppy contains a Dog, and Go does not treat it as one. A function that takes a Dog parameter does not accept a Puppy:

    func takesDog(d Dog) { fmt.Println(d.name) }
    
    p := Puppy{Dog{"x"}}
    takesDog(p)      // compile error: cannot use p as Dog value
    takesDog(p.Dog)  // works
    

    I compiled the first call and got that error. To write code that accepts "anything dog-like", use an interface, which a later lesson covers. A Puppy does satisfy an interface whenever its promoted and own methods match it.

    Fields you add, and more than one embed

    The outer struct can declare fields of its own next to the embedded type, for example toy string in Puppy. A struct may also embed several types. If two embedded types promote the same name at the same depth, using that name through the outer value is ambiguous and the compiler rejects it until you write the full path, such as p.Dog.name.

    Initialising the embedded part

    The embedded field is set in a struct literal by using the type name as the key, with a nested literal for its value:

    func NewPuppy(name string) Puppy {
        return Puppy{Dog: Dog{name: name}}
    }
    

    This plays the role that calling a parent constructor plays in other languages. If you leave Dog: ... out, the embedded Dog is simply its zero value, with an empty name.

    The lesson's exercise

    The starter code has Dog with bark, an empty Puppy struct, and an unfinished NewPuppy.

    1. Embed Dog in Puppy as an anonymous field, so its fields and methods are promoted.
    2. Finish NewPuppy by returning a Puppy whose Dog field is a Dog literal holding the name.
    3. Add whine with a Puppy receiver named this, and print whimper with fmt.Println.
    4. Write mainFunction: create buddy with NewPuppy("Buddy"), call the promoted bark, then call whine.
    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) whine() {
        fmt.Println("whimper")
    }
    
    
    
    func mainFunction() {
        buddy := NewPuppy("Buddy")
        buddy.bark()
        buddy.whine()
    }
    

    The output is:

    Woof! I am Buddy
    whimper
    

    The call buddy.bark() runs Dog's method with this bound to the embedded Dog, not to the whole Puppy. That detail matters for overriding, covered in the next lesson.

    Common mistakes

    • Naming the field. dog Dog is an ordinary named field, and nothing is promoted: you would have to write p.dog.bark().
    • Writing Puppy{name: name}. Promoted fields cannot be used as keys in a literal. Use Puppy{Dog: Dog{name: name}}.
    • Passing a Puppy where a Dog is required. Pass p.Dog or use an interface.

    Key takeaways

    • Embed a type by listing it without a field name.
    • Its fields and methods are promoted to the outer type.
    • Build the embedded part with the type name as the key in the literal.
    • Embedding is composition: the outer type is not a subtype of the embedded one.
    Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free