Compile Ready
All low level design problems
Low Level Design/Concepts & Foundations/ Concept

Creational Patterns

Singleton, Factory, Abstract Factory, Builder, and Prototype — how to control object creation cleanly.

6m readPatternsCreationalFactoryBuilderSingleton

Introduction

Creational design patterns answer a core LLD question: who is allowed to create objects, when are they created, and how do callers avoid coupling to construction details.

In interviews at Amazon, Microsoft, and Google, these patterns show whether you can separate object creation from business logic. Singleton controls one shared instance, Factory Method delegates one product creation step to subclasses, Abstract Factory creates related product families, Builder assembles complex objects safely, and Prototype copies configured objects without rebuilding them from scratch.

Learning Objectives

  • Explain the intent, use cases, and pitfalls of Singleton, Factory Method, Abstract Factory, Builder, and Prototype.

  • Implement thread-safe Singleton variants in Java: eager initialization, double-checked locking, enum singleton, and Bill Pugh holder.

  • Choose Factory Method or Abstract Factory based on whether one product or a related family of products must vary.

  • Use Builder to avoid telescoping constructors and preserve invariants for complex immutable objects.

  • Use Prototype when cloning a preconfigured object is cheaper or clearer than rebuilding it.

  • Call out interview tradeoffs, especially Singleton as a global-state anti-pattern when overused.

Core Theory

Creational patterns isolate object construction

A creational pattern is useful when the client should not know the exact class, constructor sequence, dependency wiring, or configuration needed to create an object.

The design goal is not to hide every constructor. The goal is to put creation logic at the boundary where variation belongs:

  • A single controlled instance belongs behind Singleton only when shared identity is required.
  • A single varying product belongs behind Factory Method.
  • A family of compatible products belongs behind Abstract Factory.
  • A multi-step object with optional fields belongs behind Builder.
  • A copied configured baseline belongs behind Prototype.

In LLD rounds, state the creation problem first, then introduce the pattern. That makes the pattern feel like a design consequence rather than memorized vocabulary.

Singleton: one controlled instance

Intent: Ensure exactly one instance of a class is used where a shared identity is required, and provide a controlled access point.

Thread-safe variants: Eager initialization is simple and safe but creates the object even if unused. Double-checked locking is lazy and safe only with a volatile field. Enum singleton is concise, serialization-safe, and reflection-resistant for most practical cases. Bill Pugh holder uses class loading to get lazy initialization without explicit synchronization.

When to use: Configuration registries, process-wide metrics collectors, stateless service locators in legacy code, and infrastructure objects that truly have one process-level identity.

Pitfalls: Singleton often becomes a global-state anti-pattern. It hides dependencies, makes tests order-dependent, complicates parallel tests, and encourages unrelated classes to reach into shared mutable state. Prefer dependency injection unless one-instance identity is the actual requirement.

public final class EagerConfig {
    private static final EagerConfig INSTANCE = new EagerConfig();

    private EagerConfig() {
    }

    public static EagerConfig getInstance() {
        return INSTANCE;
    }
}

public final class DclConfig {
    private static volatile DclConfig instance;

    private DclConfig() {
    }

    public static DclConfig getInstance() {
        DclConfig local = instance;
        if (local == null) {
            synchronized (DclConfig.class) {
                local = instance;
                if (local == null) {
                    local = new DclConfig();
                    instance = local;
                }
            }
        }
        return local;
    }
}

public enum EnumConfig {
    INSTANCE;

    public String region() {
        return "us-east-1";
    }
}

public final class HolderConfig {
    private HolderConfig() {
    }

    private static class Holder {
        private static final HolderConfig INSTANCE = new HolderConfig();
    }

    public static HolderConfig getInstance() {
        return Holder.INSTANCE;
    }
}

Factory Method: defer one product choice

Intent: Define an operation for creating one product, but let subclasses or specialized creators decide the concrete product class.

When to use: A workflow is stable but one created object varies, such as notification channels, document parsers, payment processors, exporters, or storage adapters. The base creator owns the common flow, and subclasses supply the product.

Pitfalls: Do not create a factory hierarchy when a simple constructor or static helper is enough. Factory Method can create many small classes, and it is easy to confuse it with Abstract Factory. If you are producing one product type, Factory Method is likely enough; if you are producing a family of compatible products, consider Abstract Factory.

interface Notification {
    void send(String message);
}

final class EmailNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("Email: " + message);
    }
}

final class SmsNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("SMS: " + message);
    }
}

abstract class NotificationCreator {
    public final void notifyUser(String message) {
        Notification notification = createNotification();
        notification.send(message);
    }

    protected abstract Notification createNotification();
}

final class EmailCreator extends NotificationCreator {
    @Override
    protected Notification createNotification() {
        return new EmailNotification();
    }
}

final class SmsCreator extends NotificationCreator {
    @Override
    protected Notification createNotification() {
        return new SmsNotification();
    }
}

Abstract Factory: create compatible families

Intent: Provide an interface for creating related products without binding clients to their concrete classes.

When to use: A client must work with a product family that must stay compatible, such as light theme widgets, dark theme widgets, cloud provider clients, database-specific repositories, or platform-specific UI controls. The client receives a factory and asks it for each related product.

Pitfalls: Abstract Factory can become heavy if there are too many products or frequent additions to the family. Adding a new product type requires changing every factory implementation. Use it when family compatibility is important enough to justify that structure.

interface Button {
    void render();
}

interface Checkbox {
    void render();
}

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

final class LightButton implements Button {
    @Override
    public void render() {
        System.out.println("Light button");
    }
}

final class LightCheckbox implements Checkbox {
    @Override
    public void render() {
        System.out.println("Light checkbox");
    }
}

final class DarkButton implements Button {
    @Override
    public void render() {
        System.out.println("Dark button");
    }
}

final class DarkCheckbox implements Checkbox {
    @Override
    public void render() {
        System.out.println("Dark checkbox");
    }
}

final class LightThemeFactory implements UiFactory {
    @Override
    public Button createButton() {
        return new LightButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new LightCheckbox();
    }
}

final class DarkThemeFactory implements UiFactory {
    @Override
    public Button createButton() {
        return new DarkButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new DarkCheckbox();
    }
}

final class SettingsScreen {
    private final UiFactory factory;

    SettingsScreen(UiFactory factory) {
        this.factory = factory;
    }

    public void render() {
        factory.createButton().render();
        factory.createCheckbox().render();
    }
}

Builder: construct complex objects safely

Intent: Separate construction of a complex object from its representation so callers can assemble it step by step while the object remains valid when built.

When to use: An object has many optional fields, validation spans multiple fields, construction should read fluently, or immutable objects would otherwise need many overloaded constructors. Builder is common for requests, configurations, test fixtures, and domain objects with optional attributes.

Pitfalls: Builder is overkill for small objects with two or three required fields. A weak builder can also delay validation too long or allow partially valid states to leak. Keep the built object immutable and validate in build.

import java.util.List;

public final class OrderRequest {
    private final String customerId;
    private final List<String> itemIds;
    private final String couponCode;
    private final boolean giftWrap;

    private OrderRequest(Builder builder) {
        this.customerId = builder.customerId;
        this.itemIds = List.copyOf(builder.itemIds);
        this.couponCode = builder.couponCode;
        this.giftWrap = builder.giftWrap;
    }

    public static Builder builder(String customerId, List<String> itemIds) {
        return new Builder(customerId, itemIds);
    }

    public static final class Builder {
        private final String customerId;
        private final List<String> itemIds;
        private String couponCode;
        private boolean giftWrap;

        private Builder(String customerId, List<String> itemIds) {
            this.customerId = customerId;
            this.itemIds = itemIds;
        }

        public Builder couponCode(String couponCode) {
            this.couponCode = couponCode;
            return this;
        }

        public Builder giftWrap(boolean giftWrap) {
            this.giftWrap = giftWrap;
            return this;
        }

        public OrderRequest build() {
            if (customerId == null || customerId.isBlank()) {
                throw new IllegalArgumentException("customerId is required");
            }
            if (itemIds == null || itemIds.isEmpty()) {
                throw new IllegalArgumentException("at least one item is required");
            }
            return new OrderRequest(this);
        }
    }
}

Prototype: copy a configured baseline

Intent: Create new objects by copying an existing prototype instead of constructing from scratch.

When to use: Object creation is expensive, setup is repetitive, or the starting configuration is known at runtime. Examples include document templates, game object templates, workflow definitions, form schemas, and preconfigured rules.

Pitfalls: Cloning can be dangerous when objects hold mutable nested state. Decide between shallow copy and deep copy explicitly. Avoid Java Object clone unless you can explain its limitations; a copy method or copy constructor is often clearer and safer.

import java.util.List;

interface DocumentPrototype {
    DocumentPrototype copy();
}

public final class ReportTemplate implements DocumentPrototype {
    private final String title;
    private final List<String> sections;

    public ReportTemplate(String title, List<String> sections) {
        this.title = title;
        this.sections = List.copyOf(sections);
    }

    @Override
    public ReportTemplate copy() {
        return new ReportTemplate(title, sections);
    }

    public ReportTemplate withTitle(String newTitle) {
        return new ReportTemplate(newTitle, sections);
    }
}

final class ReportService {
    private final ReportTemplate monthlyTemplate;

    ReportService(ReportTemplate monthlyTemplate) {
        this.monthlyTemplate = monthlyTemplate;
    }

    public ReportTemplate createMonthlyReport(String title) {
        return monthlyTemplate.copy().withTitle(title);
    }
}

Choosing the right creational pattern

Start from the variation point. If there is no variation and construction is simple, use a constructor. If construction varies by one product, use Factory Method. If compatible families vary together, use Abstract Factory. If construction has many optional steps, use Builder. If a runtime template should be copied, use Prototype.

Treat Singleton as the exception. It answers an identity constraint, not a general dependency-management problem. In modern Java systems, dependency injection often gives one shared instance per container while keeping dependencies explicit and testable.

Diagrams

Builder separates step-by-step assembly from the final object

Rendering diagram…
The builder owns construction steps and validation. The final Meal can stay immutable, while a director can reuse common assembly recipes without becoming part of the Meal class.

Comparisons

Factory Method vs Abstract Factory

DimensionFactory MethodAbstract FactoryInterview guidance
Main problemOne product choice varies inside a larger workflowA family of related products must vary togetherAsk whether callers need one product or multiple compatible products
Typical shapeAn abstract creator defines a creation methodA factory interface exposes several creation methodsFactory Method is usually smaller and easier to introduce
Client dependencyClient may use a creator subclass for the product it needsClient receives one family factory and creates related products from itAbstract Factory keeps product compatibility centralized
Adding a new product familyOften add a new creator subclassAdd a new concrete factory implementing the same family methodsBoth handle new variants well when interfaces are stable
Adding a new product typeUsually unrelated unless the single product abstraction changesRequires updating the factory interface and all concrete factoriesAbstract Factory is less convenient when the family keeps gaining new product types
ExampleEmailCreator creates EmailNotificationDarkThemeFactory creates DarkButton and DarkCheckboxUse examples that make the scope of creation obvious

Builder vs telescoping constructors

DimensionBuilderTelescoping constructorsInterview guidance
ReadabilityNamed fluent steps reveal the meaning of each optional valueMany overloaded constructors force callers to remember argument orderUse Builder when constructor calls become hard to read
ValidationCentral validation can run once in buildValidation is duplicated or scattered across overloadsBuilder works well when fields have cross-field constraints
ImmutabilityFinal object can be immutable while the builder is mutableFinal object can also be immutable but construction is less expressivePrefer immutable built objects for domain requests and configs
CostAdds an extra class or nested classHas fewer types for small objectsDo not introduce Builder for tiny value objects
EvolutionNew optional fields can be added with new builder methodsNew optional fields often create more overloadsBuilder is safer for APIs that evolve over time

Best Practices

  • Use the simplest construction mechanism that keeps callers decoupled and the object valid.

  • Explain the creation variation before naming the pattern in an interview.

  • Keep constructors visible when construction is simple; not every class needs a factory.

  • Prefer dependency injection over Singleton when you only need shared service reuse.

  • Make Singleton instances immutable or carefully synchronized if they hold state.

  • For double-checked locking, use a volatile instance field or choose a simpler Singleton variant.

  • Use Abstract Factory only when product compatibility across a family matters.

  • Validate required fields in Builder build and return immutable objects.

  • For Prototype, document whether copying is shallow or deep and protect mutable nested collections.

Common Mistakes

  • ×

    Using Singleton as a convenient global variable instead of passing dependencies explicitly.

  • ×

    Writing double-checked locking without volatile, which can expose unsafe publication.

  • ×

    Adding factories for every class and making the design harder to navigate than constructors.

  • ×

    Using Factory Method for a whole product family and scattering compatibility rules across creators.

  • ×

    Using Abstract Factory when only one product varies, creating unnecessary interfaces.

  • ×

    Creating a Builder that allows build to return an invalid object.

  • ×

    Forgetting defensive copies in Builder or Prototype when lists and maps are mutable.

  • ×

    Using Java Object clone without handling deep copy, final fields, and constructor invariants clearly.

Quiz

0/7 answered

  1. 1.Which Singleton variant is usually the simplest serialization-safe Java choice?

  2. 2.Why must the instance field be volatile in double-checked locking?

  3. 3.When should Factory Method be preferred over Abstract Factory?

  4. 4.What is the primary benefit of Abstract Factory?

  5. 5.Which object is the best fit for Builder?

  6. 6.What is the main risk in Prototype?

  7. 7.What is the strongest critique of overusing Singleton?

Flashcards

Cheat Sheet

Creational pattern goal: Keep construction logic near the variation point and away from business workflows.

Singleton: Use only for true one-instance identity. Prefer enum or Bill Pugh holder for simple thread safety. Use double-checked locking only with volatile. Avoid mutable global state.

Factory Method: One product varies. The base creator owns the workflow and delegates product creation to a method implemented by subclasses or specialized creators.

Abstract Factory: A family of products varies together. The client receives one factory and creates compatible products from that family.

Builder: Many required and optional construction inputs. Builder collects values, validates in build, and returns an immutable final object.

Prototype: Copy a configured object when rebuilding is expensive or when templates are selected at runtime. Be explicit about shallow versus deep copying.

Interview shortcut: Constructor for simple creation, Factory Method for one varying product, Abstract Factory for related families, Builder for complex construction, Prototype for copying, Singleton only for genuine shared identity.

References