# Composition over inheritance

> Build behaviour by combining small objects that hold and delegate to each other, rather than by extending class hierarchies.

Concept · Code design · https://levelcode.org/concepts/composition-over-inheritance

**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 `FlyingFish` a `Fish` or a `Bird`? 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](https://levelcode.org/concepts/liskov-substitution) violations.

## Composition instead

Compose an object from parts that each do one thing, behind small interfaces:

```go
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](https://levelcode.org/roadmaps/software-engineer/design-patterns) 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](https://levelcode.org/roadmaps/software-engineer/solid).
