Concept · Code design
Composition over inheritance
Build behaviour by combining small objects that hold and delegate to each other, rather than by extending class hierarchies.
Composition over inheritance, popularised by the Design Patterns book (1994), advises that objects gain behaviour by holding other objects that provide it, rather than by inheriting it from a parent class.
The problem with deep inheritance
Inheritance couples a subclass to its parent's implementation, not just its interface:
- Fragile base class: a change in the parent — even an internal one, like which method calls which — can silently break subclasses.
- Rigid hierarchies: behaviours that don't fit one tree force awkward choices. Is
FlyingFishaFishor aBird? Real requirements rarely respect a single taxonomy. - Inherited baggage: subclasses get every method of the parent, including ones that don't make sense for them — a classic route to Liskov substitution violations.
Composition instead
Compose an object from parts that each do one thing, behind small interfaces:
type Notifier struct {
sender Sender // email, SMS, push — swappable
limit *RateLimiter // shared policy
log *slog.Logger
}
func (n *Notifier) Notify(ctx context.Context, userID, msg string) error {
if !n.limit.Allow(userID) {
return ErrRateLimited
}
return n.sender.Send(ctx, userID, msg)
}Each part can be replaced, tested or reused independently, and new combinations need no new subclass. Patterns like strategy and decorator are composition by design.
In Go and TypeScript
Go has no inheritance. Struct embedding looks similar but is composition with automatic method forwarding: the embedded type doesn't know about the outer type, and there's no virtual dispatch from the inner type back to the outer one. Interfaces provide polymorphism without a hierarchy.
TypeScript supports classes and extends, but idiomatic modern TypeScript — and React since hooks — leans on functions, interfaces and composition. One level of inheritance from a framework base class is usually fine; deep hierarchies of your own rarely are.
Inheritance still fits genuine, stable "is-a" relationships with little shared state. The default should be composition. See the SOLID lesson.