Go, End to End
Composition Over Inheritance
Go has no `class` keyword, no `extends`, no `super`. This is a deliberate design choice, not a missing feature, and fighting it by trying to recreate inheritance with embedding tends to produce worse Go code than just designing around composition and interfaces from the start.
Why Go left inheritance out
Classic inheritance couples a type to its entire ancestor chain. Change a method on a base class, and every subclass three levels down might silently behave differently. Go's designers considered this a long-term liability for large codebases, and replaced it with two independent, more composable tools: embedding (for reusing implementation) and interfaces (for substitutability). The two together cover most of what inheritance is used for, without the coupling.
The inheritance-shaped problem
Say you're modeling shapes, and you want every shape to support an `Area()` method, with some shared logic for logging or comparison. In an inheritance-based language, the instinct is a base `Shape` class with `Area()` as a virtual method, and `Circle`/`Rectangle` subclasses overriding it.
The Go way: interface for the contract, no shared base type at all
No base class exists anywhere. `Circle` and `Rectangle` share nothing but the fact that both happen to have an `Area()` method, and that's entirely sufficient for `totalArea` to treat them uniformly through the `Shape` interface.
When you do want shared implementation: embedding
Sometimes you genuinely want to share behavior, not just a contract, say several shapes should all log the same way when their area is computed. That's when embedding earns its keep: it reuses an implementation without pretending there's an is-a relationship.
The tell that separates this from real inheritance: nothing anywhere treats a Circle as substitutable for a Logger. There's no function signature anywhere that says "takes a Logger" and would silently also accept a Circle just because it embeds one. Embedding only promotes fields and methods, it grants zero polymorphism.
Putting both together
The idiomatic pattern is almost always: interfaces define what a type can do (for callers who only care about behavior), embedding shares how a type does part of its work (for types that want to reuse an implementation), and the two rarely need to touch. A type can embed a helper struct for shared plumbing and independently satisfy any number of unrelated interfaces based purely on its method set.
A checklist for the instinct to reach for inheritance
- Do you actually need substitutability (treating several concrete types the same way through one abstraction)? That's an interface, full stop, not embedding.
- Do you just want to avoid retyping the same three methods on five structs? That's embedding a shared helper struct, and it's fine, just don't expect it to grant any polymorphism.
- Are you modeling a hierarchy because the domain genuinely has one (a Manager 'is a kind of' Employee, say)? Consider whether composition (Employee has a Manager field, or a Role interface) models it just as well without the coupling. Usually it does.