Home › Low-Level Design › Abstract Factory Pattern
A

Abstract Factory Pattern

Abstract Factory builds whole families of related objects without your code ever naming the concrete classes. You reach for it when you have several things that must agree with each other, and you want to pick the right set once and never think about it again. In an LLD loop the bar is not "recite the GoF definition." It is: spot the family constraint in a vague prompt, draw the diagram in under two minutes, and name the one tradeoff that makes a senior engineer trust you.

Low-Level DesignMedium10 min read

If you remember five things

The whole pattern in one glance. Everything below defends these five calls.

  1. Intent. Produce families of matching objects (a button plus a checkbox plus a scrollbar that all belong together) behind one interface, so the client never names a concrete class.
  2. The family constraint. Use it only when mixing families is a bug: a MacButton next to a WindowsCheckbox should be impossible, and the factory makes it impossible.
  3. Structure. One abstract factory interface, one concrete factory per family, abstract products, concrete products, and a client that touches only interfaces.
  4. Single decision point. The one place that picks a family is the composition root. Everything downstream stays blind to which family it got.
  5. The classic gotcha. It is Open/Closed along the family axis and hostile along the product-kind axis: a new product kind forces a new method on every concrete factory.

1The problem it solves#

One scenario shows up over and over: you are building a UI toolkit and need buttons, checkboxes, and scrollbars. Then the ask lands: "support macOS and Windows, make it look native on both."

The combinatorics get ugly fast.

  • Two platforms times three widget kinds is already six concrete classes (MacButton, WindowsButton, MacCheckbox, and so on).
  • A third platform pushes it to nine.
  • Each one is a class you now have to create from somewhere.

The naive version branches everywhere. At every spot you build a widget you write:

if (platform == MAC) {
    button = new MacButton();
} else {
    button = new WindowsButton();
}
  • You write that same if for the checkbox. And the scrollbar. And the menu.
  • Three widget kinds and two platforms means three copies of the same branch, each a place a future bug can hide.
  • The branching spreads through the whole codebase like mold.

There are two real problems underneath, and the second is the sneaky one.

  • Open/Closed violation. Business code knows every concrete class, so a third platform means editing the branch in every file. You cannot extend the system without reopening existing code.
  • No pairing guarantee. Nothing stops you from pairing a MacButton with a WindowsCheckbox. The families are supposed to stay together, but the type system lets you mix them freely. The compiler will never catch that bug.
What Abstract Factory actually buys you

It fixes both at once. One object decides the whole family, and your code just asks it for parts. The client stops knowing concrete classes (Open/Closed is restored), and a single factory has no code path that can produce a mismatched part (the pairing bug becomes structurally impossible, not merely discouraged).

2The idea (mental model)#

Think of a kit. When you buy a furniture kit you get screws, bolts, and panels that all fit each other. You do not pick a screw individually and hope it matches the panel. The kit guarantees the fit.

  • An abstract factory is that kit: an object whose entire job is to hand you a set of parts that belong together.
  • You never say "give me a MacButton." You say "give me a button" to the Mac kit, and it decides.
  • The one line to keep in your head: a factory of consistent sets. Choose the set once, at startup, and every part you pull after that is guaranteed to be from the same family.

Two principles do the heavy lifting.

  • Dependency Inversion. The client depends on the Button and GUIFactory abstractions, never on MacButton.
  • Single Responsibility. The only thing that knows how to build a family is the factory for that family, so widget-building logic lives in one place per platform, not scattered across the app.
Makes a family
Abstract Factory
Produces
A whole matching set of products.
Mechanism
Composition: you hold a factory object.
Shape
Several create methods behind one interface.
Grows well when
You add new families.
Makes one thing
Factory Method
Produces
One kind of product.
Mechanism
Inheritance: you override one method.
Shape
A single overridable creation hook.
Grows well when
You subclass to vary one product.

Interviewers love this exact distinction, so have the one-liner ready: Factory Method is one hook; Abstract Factory is a whole menu of hooks bundled behind one interface.

3Structure#

The moving parts, each with one job:

  • Abstract Factory. An interface that declares one creation method per product kind: createButton(), createCheckbox().
  • Concrete Factories. One per family: MacFactory, WindowsFactory. Each returns that family's products.
  • Abstract Products. The interfaces for each kind of thing: Button, Checkbox.
  • Concrete Products. The actual classes: MacButton, WindowsCheckbox.
  • Client. Your code. Holds a reference to the abstract factory and the abstract products, and never mentions a concrete class.

The key move: the client depends only on interfaces.

  • The concrete factory is injected once, usually at the composition root.
  • Everything downstream stays blind to which family it got.
  • That single injection point is where the design earns its testability: pass a fake factory in a unit test and the client never knows the difference.
classDiagram class GUIFactory { +createButton() Button +createCheckbox() Checkbox } class MacFactory { +createButton() Button +createCheckbox() Checkbox } class WindowsFactory { +createButton() Button +createCheckbox() Checkbox } class Button { +render() void } class Checkbox { +render() void } class MacButton { +render() void } class WindowsButton { +render() void } class MacCheckbox { +render() void } class WindowsCheckbox { +render() void } class Application { -Button button -Checkbox checkbox } GUIFactory <|-- MacFactory GUIFactory <|-- WindowsFactory Button <|-- MacButton Button <|-- WindowsButton Checkbox <|-- MacCheckbox Checkbox <|-- WindowsCheckbox MacFactory ..> MacButton MacFactory ..> MacCheckbox WindowsFactory ..> WindowsButton WindowsFactory ..> WindowsCheckbox Application o-- GUIFactory Application o-- Button Application o-- Checkbox
Notice the dotted arrows: they are the only lines that reach a concrete class, and every one of them starts at a factory. The Application on the left touches interfaces only. That is the pattern's whole shape in one picture: concrete knowledge is quarantined inside the factories.

4Implementation in Java#

Start with the product interfaces. Keep them small.

interface Button {
    void render();
}

interface Checkbox {
    void render();
}

Now the concrete products, one set per platform.

class MacButton implements Button {
    public void render() {
        System.out.println("Rounded macOS button");
    }
}

class MacCheckbox implements Checkbox {
    public void render() {
        System.out.println("macOS checkbox with a soft check");
    }
}

class WindowsButton implements Button {
    public void render() {
        System.out.println("Square Windows button");
    }
}

class WindowsCheckbox implements Checkbox {
    public void render() {
        System.out.println("Windows checkbox with a hard tick");
    }
}

The factory interface. One method per product kind. This is the whole contract, and its size is the thing to watch: every method here must be implemented by every concrete factory, so this interface is the pattern's cost center.

interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

Two concrete factories. Each produces only its own family, so the pairing can never go wrong.

class MacFactory implements GUIFactory {
    public Button createButton() {
        return new MacButton();
    }
    public Checkbox createCheckbox() {
        return new MacCheckbox();
    }
}

class WindowsFactory implements GUIFactory {
    public Button createButton() {
        return new WindowsButton();
    }
    public Checkbox createCheckbox() {
        return new WindowsCheckbox();
    }
}

The client. Notice it never says new MacButton(). It takes a factory, builds its parts, and uses them, with no idea which platform it is on.

class Application {
    private final Button button;
    private final Checkbox checkbox;

    Application(GUIFactory factory) {
        this.button = factory.createButton();
        this.checkbox = factory.createCheckbox();
    }

    void renderUI() {
        button.render();
        checkbox.render();
    }
}
  • That GUIFactory in the constructor is textbook constructor injection.
  • It is why you can test Application against a stub factory with zero production classes involved.

The one place that picks the family. This is the composition root, the single spot allowed to know about concrete factories.

public class Main {
    public static void main(String[] args) {
        String os = System.getProperty("os.name").toLowerCase();
        GUIFactory factory = os.contains("mac")
            ? new MacFactory()
            : new WindowsFactory();

        Application app = new Application(factory);
        app.renderUI();
    }
}
Pause: the naive version had that if copied once per widget kind. Where did all those branches go?
Reveal the answer
They collapsed into one. The if still exists, but it lives in exactly one line in one file, and it chooses an entire family at once instead of one widget. Three branches became one, and that one moved to the edge of the system where it belongs. Everywhere else is now clean. That collapse is the payoff you point at in the interview.

Here is the runtime flow for one call, so you can see how the request travels:

sequenceDiagram participant Main participant App as Application participant F as GUIFactory participant B as Button Main->>F: pick MacFactory Main->>App: new Application(factory) App->>F: createButton() F-->>App: MacButton App->>B: render() B-->>App: draws rounded button
The client (Application) only ever talks to the GUIFactory and Button interfaces. The concrete MacFactory was chosen in Main and handed in. Application asks for a button and gets one back without ever learning its concrete type.

5A real-world example#

The classic is cross-platform UI, and it is classic because it is true.

  • Java's own Swing look-and-feel system works on this idea: a LookAndFeel object produces a matching set of UI delegates so your buttons, sliders, and menus all share one visual language.
  • Switch the LookAndFeel and the whole set changes together. That is the pattern in production in the standard library.

It works well outside UI too. Cloud providers are a clean fit.

  • One project needed a storage client, a queue client, and a secrets client, all from the same provider or nothing lined up.
  • The build: a CloudFactory with createStorage(), createQueue(), and createSecrets().
  • AwsFactory returned S3, SQS, and Secrets Manager wrappers. GcpFactory returned the Google equivalents.
  • The app got one factory at boot, from config, and never cared again. Swapping providers in a test became a one-line change: pass the other factory, replacing what would have been three coordinated client swaps.

Database access layers that support more than one engine are another good fit.

  • A PostgresFactory and a MySqlFactory each hand back a connection, a query builder, and a dialect object that all speak the same SQL flavor.
  • You never accidentally feed MySQL syntax to a Postgres connection, because the factory will not give you that mismatched pair.

The common thread in all three: a set of parts that must stay consistent, and a single decision point that picks the set.

6When to use it, and when not to#

Use it when:

  • You have two or more product kinds that come in matching families, and mixing families across the line is a bug.
  • You want to swap the whole set at once, at runtime or by config, without touching client code.
  • The concrete classes are a detail your business logic should not know.

Skip it when:

  • You only have one product kind. Then you want Factory Method or a plain factory. Abstract Factory earns its keep only when the family has multiple members.
  • The families never actually vary. One platform that will only ever be one platform makes this ceremony with no payoff: a kit factory that produces a single kit.
  • Adding a new product kind is more likely than adding a new family. This is the pattern's real weakness, covered next.
The gotcha that separates memorizers from engineers

Every new product kind means a new method on the factory interface and a new method in every concrete factory. Two factories means three edits for one new widget kind; five factories means six edits. If you add product kinds often, the interface churns constantly. Abstract Factory is happy when families grow and hostile when the product list grows. If you cannot say this out loud unprompted, the interviewer reads you as someone who memorized the shape without understanding the cost.

1 vs N
The two axes that decide everythingAdding a new family is 1 new class and zero edits to existing code (Open/Closed wins). Adding a new product kind is N+1 edits, one per existing factory plus the interface. Pick this pattern only when you expect new families, not new kinds.

7Common mistakes#

Confusing it with Factory Method.

  • The single most common slip. Factory Method overrides one creation step through inheritance.
  • Abstract Factory bundles several creation methods behind one interface, held by composition, to produce a whole family.
  • If someone's "abstract factory" has one create method, it is really a factory method.

Letting concrete classes leak into the client.

  • If Application ever writes new MacButton() or casts a Button down to MacButton, the pattern is broken.
  • One sneaky instanceof check and you have undone it, because now the client knows the family and you are back to the branching you started with.

Overbuilding too early.

  • People reach for this when there is one family and a vague "maybe someday" second one, ending up with four interfaces and six classes to produce one button.
  • Build the simple version first. Introduce the factory when the second family actually arrives and the pattern pays for itself.

Forgetting the families must stay matched.

  • If your MacFactory can somehow return a WindowsCheckbox, you have thrown away the guarantee that justifies the pattern.
  • Each concrete factory must return only its own family, always. The guarantee is only as strong as the weakest factory.

Scattering the family choice.

  • The if os == mac check should exist in exactly one place.
  • Select the factory in three different files and you have recreated the original mess one level up, just with fancier types.
Follow-ups the interviewer will ask (and your answers)
"What's the difference between Factory Method and Abstract Factory?"
Factory Method makes one product through inheritance: you override a single creation method in a subclass. Abstract Factory makes a family of products through composition: you hold a factory object with several create methods behind one interface, and each concrete factory produces the whole matching set. One hook versus a bundled menu of hooks.
"How would you add a new platform?"
Write one new concrete factory, implement each create method to return that family's products, and register it at the composition root. Zero changes to client code. That is the Open/Closed Principle paying off along the family axis, and it is the pattern's main selling point.
"What's the downside?"
Adding a new product kind forces a change to the factory interface and every concrete factory. With N families that is N+1 edits for one new kind. The pattern optimizes for new families, not new kinds, so you only reach for it when you expect the family set to grow.
"Design a notification system for email, SMS, and push across multiple providers. Does this fit?"
Yes, if each provider hands you a matching set: a sender, a template renderer, and a formatter that must all come from the same provider. That is a family, so a `ProviderFactory` per provider fits. If each provider gives you only one kind of object, it is a plain Factory Method, not Abstract Factory. Spotting that distinction is the point of the question.
"Someone calls a class with a single create method an Abstract Factory. Are they right?"
No. One create method is a factory method or a simple factory. Abstract Factory needs at least two product kinds bundled behind one interface so the factory can enforce that they come from the same family. Without the family constraint there is nothing for the pattern to guarantee.

What you can now explain in the room

  • The exact two problems it solves: the Open/Closed violation and the impossible-to-catch family-mixing bug.
  • The structure (abstract factory, concrete factories, abstract and concrete products, client) and how to draw it in under two minutes.
  • Why the client depends only on interfaces, and how that single injection point makes testing trivial.
  • The one-liner that distinguishes it from Factory Method: a family through composition versus one product through inheritance.
  • How adding a family is cheap (one class, zero edits) while adding a product kind is expensive (N+1 edits).
  • When to skip it entirely: one product kind, families that never vary, or product kinds that churn more than families.

Next, compare this with the Factory Method and Builder patterns, and try applying it to a notification-service or payment-gateway design problem.

Get a prep plan built around your Abstract Factory Pattern interview

Tell us the role and your interview date. We will send back a dated, day by day plan. Free.

Thanks. Check your inbox, your Abstract Factory Pattern prep plan is on the way.