Beginner14 min readDesign Patternslive prototype

Abstract Factory

Build a whole family of matching objects from one factory, so they always go together. Pick the Dark theme factory and every widget it makes — button, checkbox, card — comes out dark and consistent, and you can never accidentally mix a dark button with a light card.

The idea

What it is

Abstract Factory is a pattern for building a whole family of matching objects in one go, so they always fit together. Instead of creating each object on its own and hoping they match, you pick one factory and it hands you the complete, guaranteed-consistent set.

Think of a UI theme kit. An app's widgets must all share one look — you'd never ship a screen with a dark button next to a light card; it looks broken. So imagine a factory for each theme. The LightThemeFactory knows how to make a light button, a light checkbox, and a light card. The DarkThemeFactory makes the dark versions. Your app picks one factory at the start, asks it for a button, a checkbox, and a card — and every piece comes out in the same theme, automatically. You never mix and match by hand, so you can never get it wrong.

The one sentence to remember

An Abstract Factory makes a family of related objects that must go together — you choose one factory, and everything it builds is guaranteed to match.

Factory Method vs Abstract Factory — the key difference

Factory Method (the previous lesson) creates one product through a method you can override. Abstract Factory creates a whole family of related products — a button and a checkbox and a card — that all belong to the same set. If you only need one kind of thing, use Factory Method. When several things must be created together and stay consistent, that's Abstract Factory.

Mechanics

How it works

The four moving parts

Abstract Factory has four roles. They look like a lot at first, but each one is small, and together they lock in the 'always matching' guarantee:

  • Abstract products — one interface per kind of thing in the family: Button, Checkbox, Card. These say what a button can do, not what it looks like.
  • Concrete products — the real theme versions: LightButton, DarkButton, LightCard, DarkCard, and so on. Each one implements its abstract product.
  • The abstract factory — one interface, ThemeFactory, with a create method per product: createButton(), createCheckbox(), createCard().
  • Concrete factoriesLightThemeFactory, DarkThemeFactory, OceanThemeFactory. Each one builds its own matching set of concrete products, and nothing else.
typescript
interface Button   { render(): string; }
interface Checkbox { render(): string; }
interface Card     { render(): string; }

// One factory interface for the whole family.
interface ThemeFactory {
  createButton(): Button;
  createCheckbox(): Checkbox;
  createCard(): Card;
}

// A concrete factory builds ONLY its theme's matching set.
class DarkThemeFactory implements ThemeFactory {
  createButton()   { return new DarkButton(); }
  createCheckbox() { return new DarkCheckbox(); }
  createCard()     { return new DarkCard(); }
}

Notice that DarkThemeFactory can only make dark parts. There is no method on it that returns a light card. That's the whole trick: because a single factory builds the entire family, the family can't be mixed. Consistency isn't something the developer has to remember — it's built into the structure.

The client stays theme-agnostic

The best part is the code that uses the widgets. It receives some ThemeFactory and never asks which one. It just calls createButton(), createCheckbox(), createCard() and lays them out. It has no idea whether it's building a light UI or a dark one — and it doesn't care:

typescript
function buildUI(factory: ThemeFactory) {
  const button   = factory.createButton();
  const checkbox = factory.createCheckbox();
  const card     = factory.createCard();
  // The client never mentions Light or Dark — it just wires them up.
  return [button.render(), checkbox.render(), card.render()];
}

buildUI(new DarkThemeFactory());   // a fully dark UI
buildUI(new LightThemeFactory());  // the same code, a fully light UI

Swap the family in one line

Because the client only depends on the interfaces, switching the entire look of the app is a one-line change: pass a different factory. Every widget re-skins together, and it's impossible to get a half-updated screen.

Adding a new theme is cheap

Want an Ocean theme? Write OceanButton, OceanCheckbox, OceanCard, and one new OceanThemeFactory that builds them. You add code without touching any existing factory or the client. That's the Open/Closed Principle in action: open for extension (new themes), closed for modification (nothing old changes). The catch to know: adding a new kind of product — say every theme now needs a Slider too — means editing the ThemeFactory interface and every concrete factory. Families are easy to add; new family members are not.

Interactive prototype

See it. Build it. Break it.

A sandboxed, hands-on simulation — no setup, no install. Play with it as you read.

About this simulation

A live UI theme kit. Pick one of three theme chips — ☀️ Light, 🌙 Dark, or 🌊 Ocean — and that single choice instantly builds a matching set of three real widgets inside a phone-style preview: a styled Button, a Checkbox with a label, and a Card. Switch themes and the whole family re-skins together, always consistent. Beside it, a client-code panel stays byte-for-byte identical across every theme (const ui = ThemeFactory.for(theme)render(...)), tagged client code: unchanged ✓ — proof that one factory swap re-skins everything. Then hit ⚠ Try to mix manually to switch to a mode with three separate pickers: choose a Dark button and a Light card and a red consistency check: ✗ MISMATCH banner appears over the ugly clashing result. Flip back to factory mode and it returns to ✓ consistent — the Abstract Factory makes the mismatch impossible. The single explain panel narrates which family was built each time.

Hands-on

Try these yourself

Open the prototype above, predict what happens, then verify.

try 01

Build a matching family in one click

Open the prototype in factory mode. Click the 🌙 Dark chip. Watch all three widgets in the preview — Button, Checkbox, and Card — appear together in the dark theme. Now click ☀️ Light, then 🌊 Ocean. Each click swaps the whole family at once, and every widget always matches. That single chip is you choosing one concrete factory.

try 02

Notice the client code never changes

Keep switching themes and watch the client-code panel on the right. The lines const ui = ThemeFactory.for(theme) and render(ui.createButton(), ui.createCheckbox(), ui.createCard()) stay exactly the same for Light, Dark, and Ocean — it's tagged 'client code: unchanged ✓'. Only the factory you passed in changed. That's the client staying theme-agnostic.

try 03

Try to break it, and see why you can't (in real code)

Click ⚠ Try to mix manually to enter the danger mode with three separate pickers. Give the button one theme and the card another — a red 'consistency check: ✗ MISMATCH' banner appears over the clashing result. Now switch back to factory mode: it snaps to '✓ consistent'. This is the whole point — with one factory building the family, a mismatch simply can't happen.

In practice

When to use it — and what trips people up

Use Abstract Factory when several things must be created together and match

Reach for it whenever your program needs to build a set of related objects that only make sense as a matched group, and you may want to swap the whole set for another:

  • Cross-platform UI toolkits — a Windows widget set (Windows button, Windows scrollbar, Windows menu) versus a macOS set. One factory per platform hands your app a whole native-looking family.
  • Swappable database drivers — a connection, a command, and a reader that must all come from the same driver. A PostgresFactory or MySqlFactory builds the matching trio so they speak the same protocol.
  • Light / dark / branded theming — exactly the prototype's case: one factory per theme, and every widget it produces is guaranteed to share the look.

Don't reach for it when you only need one product

If you're only creating one kind of thing — just a button, with no related checkbox or card that has to match — an Abstract Factory is overkill. Use Factory Method (or a plain function) instead. Abstract Factory earns its extra interfaces only when there's a genuine family whose members must stay consistent.

What it gives you

  • Guarantees a consistent family — because one factory builds the whole set, you can never accidentally mix a Dark button with a Light card.
  • The client stays decoupled from concrete classes — it works against the ThemeFactory interface and never names Light, Dark, or Ocean, so it doesn't change when themes do.
  • Swapping the whole family is one line — pass a different factory and every product re-skins together.
  • Adding a new theme is easy and safe — write one new concrete factory and its products; existing factories and client code stay untouched (Open/Closed).

Common mistakes

  • Lots of classes and interfaces — even a small family needs abstract products, concrete products, an abstract factory, and concrete factories. It's heavyweight for simple needs.
  • Adding a new product kind is painful — introducing, say, a Slider means editing the ThemeFactory interface and every single concrete factory.
  • Indirection can obscure the flow — you can't see which concrete class you'll get by reading the client; you have to trace back to which factory was passed in.
  • Easy to over-apply — reaching for it when you only ever create one product adds ceremony without any real 'family' to keep consistent.

Reference

Code & further reading

A minimal reference implementation and pointers worth bookmarking.

// --- Abstract products: what each widget can do ---
interface Button   { render(): string; }
interface Checkbox { render(): string; }

// --- Concrete products for the Light theme ---
class LightButton   implements Button   { render() { return "[ Light Button ]"; } }
class LightCheckbox implements Checkbox { render() { return "[x] Light Checkbox"; } }

// --- Concrete products for the Dark theme ---
class DarkButton   implements Button   { render() { return "[ Dark Button ]"; } }
class DarkCheckbox implements Checkbox { render() { return "[x] Dark Checkbox"; } }

// --- The abstract factory: one create method per product ---
interface ThemeFactory {
  createButton(): Button;
  createCheckbox(): Checkbox;
}

// --- Concrete factories: each builds ONLY its own matching family ---
class LightThemeFactory implements ThemeFactory {
  createButton()   { return new LightButton(); }
  createCheckbox() { return new LightCheckbox(); }
}
class DarkThemeFactory implements ThemeFactory {
  createButton()   { return new DarkButton(); }
  createCheckbox() { return new DarkCheckbox(); }
}

// --- Client: takes ANY factory, never knows the theme ---
function buildUI(factory: ThemeFactory): string[] {
  return [factory.createButton().render(), factory.createCheckbox().render()];
}

buildUI(new DarkThemeFactory());   // fully dark, guaranteed to match
buildUI(new LightThemeFactory());  // same code, fully light

References & further reading

6 sources

Knowledge check

Did it land?

Quick questions, answers revealed on submit. Sign in to save your best score.

question 01 / 05

What does the Abstract Factory pattern give you?

question 02 / 05

How does Abstract Factory differ from Factory Method?

question 03 / 05

Why is it impossible to mix a Dark button with a Light card when you use an Abstract Factory?

question 04 / 05

You want to add a brand-new Ocean theme. What does that take?

question 05 / 05

In the pattern, what does the client code know about the concrete theme?

0/5 answered