OOP & Design Patterns' own domain taught "prefer composition over inheritance" as a guideline worth following in languages that offer both. Go skips the debate entirely by never offering inheritance in the first place — there is no extends keyword, no base class, and no way to write one even if you wanted to.
3 min read
type User struct {
Name string
Email string
Age int
}
u := User{Name: "Ana", Email: "ana@example.com", Age: 29}
fmt.Println(u.Name)A struct is Go's way of grouping related fields together — conceptually similar to a Python dataclass or a plain JavaScript object, but statically typed and with no constructor, no inheritance hierarchy, and no hidden prototype chain (the mechanism JavaScript's own prototypes-and-inheritance lesson covered) sitting underneath it. A struct is exactly its declared fields, nothing implicit added by the language.
func (u User) Greet() string {
return "Hello, " + u.Name
}
u.Greet() // "Hello, Ana"Go attaches a method to a type using a receiver ((u User)) rather than defining it inside a class body — there's no class keyword at all. This is a real structural difference from OOP & Design Patterns' class-based model: a method and the struct it operates on are connected by this receiver syntax, and a single type can have methods defined across multiple files in the same package, with no single place that "is" the class definition the way a class Foo { ... } block is in other languages.
type Animal struct {
Name string
}
func (a Animal) Describe() string { return a.Name + " makes a sound" }
type Dog struct {
Animal // EMBEDDED — not inherited, no "extends"
Breed string
}
d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Labrador"}
d.Describe() // "Rex makes a sound" — promoted from the embedded Animal
d.Name // "Rex" — also promotedStruct embedding places one struct inside another without naming a field — and Go automatically "promotes" the embedded struct's fields and methods, letting d.Name and d.Describe() work as if Dog had declared them directly. This looks similar to inheritance at first glance, but the difference is structural and real: Dog doesn't become an Animal the way a subclass becomes its parent class — it merely contains one, accessible directly by promotion. There's no polymorphism through a shared base type, no method overriding in the inheritance sense, and no super call — composition over inheritance isn't a style guideline here, as it was presented in OOP & Design Patterns' own lesson on the topic; it's the only mechanism Go actually provides.
OOP & Design Patterns' own composition-over-inheritance lesson named the real problems deep inheritance hierarchies cause in practice: fragile coupling to a parent's implementation details, and hierarchies that grow rigid and hard to change as a codebase ages. Go's designers decided these problems were common and severe enough to remove the option of inheritance entirely, rather than trusting every team to consistently choose composition when both tools are available — the same reasoning lesson 1 described for the rest of the language's deliberate omissions.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. Does Go support classical inheritance?
2. What does struct embedding actually do?
3. Why does embedding differ structurally from inheritance, even though both let a type reuse another's methods?
Go