Skip to content

Design Patterns

intermediate2 min read
  • TypeScript

Named, reusable solutions to recurring design problems — a shared vocabulary for structuring code, to be applied when the problem genuinely arises.

Prerequisites

  • SOLID principles help (see Related Topics)

What you'll understand

  • What a design pattern is
  • Why patterns are a shared vocabulary, not code to copy
  • When to apply a pattern and when not to

Explanation

A design pattern is a named, reusable solution to a problem that comes up again and again in software design. Patterns are not code you copy and paste; they are general templates for how to structure code to solve a particular kind of problem. Because they are named and well known, they also form a shared vocabulary: saying "use a factory here" communicates a whole design in three words.

Patterns are traditionally grouped by intent. Creational patterns deal with how objects are made (such as Factory). Structural patterns deal with how objects are composed (such as Adapter). Behavioural patterns deal with how objects interact and share responsibility (such as Strategy and Observer). You have already met patterns in this curriculum: the Repository pattern and Dependency Injection are exactly this kind of reusable solution (see Related Topics).

The real skill is knowing when a pattern applies. A pattern used because the problem it solves is actually present makes code clearer; a pattern forced onto a problem that does not need it adds ceremony and confusion. Learn patterns so you recognise the situations they fit, then reach for one when the situation arises, not to show that you know it. The simplest design that works is still the goal.

Examples

The Strategy pattern lets you swap interchangeable behaviours behind one interface:

interface PricingStrategy {
  price(amount: number): number;
}

class StandardPricing implements PricingStrategy {
  price(amount: number) { return amount; }
}
class MemberPricing implements PricingStrategy {
  price(amount: number) { return amount * 0.9; }
}

// The checkout uses a strategy without knowing which one
class Checkout {
  constructor(private readonly pricing: PricingStrategy) {}
}

Common mistakes

  • Forcing a pattern onto a problem that does not need it.
  • Treating patterns as copy-paste code rather than adaptable templates.
  • Overengineering a simple task with layers of patterns.
  • Learning the names but not the problems each one solves.

Best practices

  • Learn patterns to recognise the problems they solve.
  • Apply a pattern only when its problem is genuinely present.
  • Use pattern names as shared vocabulary with your team.
  • Prefer the simplest design that works over an elaborate one.

Further reading

Related topics