Study Notes
Design & Patternsapplied

Creational Design Patterns

Factory Method, Builder, and Singleton from the ground up — the principle of encapsulating what varies, the step-by-step Builder with immutability, thread-safety traps in Singleton, and when to reach for each pattern.

40 min read·verified Java 21·reviewed 2026-08-30

Assumes you know

After this you should be able to

  • Choose a creational pattern by problem
  • Explain builder immutability and singleton risks
Factory MethodBuilderSingleton

Object-Oriented Design · Study Guide

How objects get built — and the three tried-and-true recipes that keep construction clean: Factory Method, Builder, and Singleton.

Factory Method Builder Singleton

Section 1 — Notebook Summary · hand-copy this

0 · The Big Idea#

  • Design pattern → reusable template for a common design problem. Not code — a recipe.
  • 3 families: Creational (object creation) · Structural (composition) · Behavioural (communication).
  • All 3 patterns here = Creational.
  • ⭐Encapsulate What Varies → find the part that changes, wall it off from the part that doesn't. Basis of nearly every pattern.

1 · Factory Method#

  • Rule → create objects without naming the exact class. Caller asks factory; factory decides.
  • Roles: Client → asks · Factory → creates · Product interface · ConcreteProducts.
  • Ex: LeetCodeCompiler → CompilationFactory.getInstance(lang) → Java/Cpp/Python compiler.
  • Win: loose coupling · easy to add languages · client never sees new JavaCompiler().
  • ⚠Adding a type still edits the factory's switch → factory isn't fully closed to change.
doodle: Client → [Factory] → picks 1 of 3 boxes stacked below, all plugged into one "Product" socket.

2 · Builder#

  • Use when → object has many fields (≥4–5), some optional, must stay immutable.
  • Rule → build step-by-step via static inner Builder, then .build().
  • Fields private final · outer constructor private, takes Builder.
  • Builder: mandatory in constructor, optional via chained setters that return this.
  • new Form.Builder("John","Doe").age(25).gender("Male").build();
Path triedWhy it failed
Telescoping ctorscombinatorial explosion, not scalable
JavaBean (setters)breaks immutability — fields mutable
Builder✓ flexible + immutable
  • ⭐Setters return this → the chaining trick. That's the whole "fluent" feel.
  • 💡Lombok @Builder = same pattern, zero boilerplate.
doodle: conveyor belt — empty tray enters left, .age .gender stamp fields on, .build() seals lid → finished immutable box.

3 · Singleton#

  • Rule → exactly ONE instance per JVM, shared everywhere. Ex: Logger, config.
  • Recipe (3 steps): 1. constructor private · 2. static getInstance() · 3. if(instance==null) create, else return existing.
  • Why: one log file · consistency · resource mgmt · less memory.
  • ⚠Naïve check if(instance==null) is not thread-safe → 2 threads can make 2 instances. +
  • 💡Fixes: eager init · synchronized · double-checked lock · enum. +
PatternOne-linerAnswers
Factoryhide which classWHICH object?
Builderassemble step-wiseHOW to build?
Singletononly one, everHOW MANY?
doodle: 3 clients → arrows → one shared box; box has a mirror/×1 label. All arrows land on the same box.

Section 2 — Detailed Study Reference

You're wiring up a class with nine fields — first name, last name, age, gender, three booleans, a city, a state. Only the first two are required. So you write a constructor. Then another for name-plus-age. Then name-plus-age-plus-gender. Ten constructors later you're staring at new Form("John", "Doe", 0, null, false, false, null, null, false) and you genuinely cannot remember what that third false means.

That mess is what creational patterns exist to prevent. They're the accumulated wisdom for one deceptively hard question: how should objects come into existence? Do it carelessly and your calling code ends up welded to concrete class names, drowning in constructor arguments, or accidentally spawning ten copies of something that was supposed to be one. This lesson walks three fixes — and the single principle underneath all of them.

§0What a design pattern actually is#

A design pattern is a tried-and-true, reusable solution to a problem that keeps showing up in software design.

Think of it as a cookbook for developers. You don't copy-paste a pattern the way you copy a library; you follow its recipe and adapt it to your kitchen. It's a template — a shared vocabulary — not a chunk of code. When one engineer says "just use a Builder here," a whole design is communicated in three words.

Patterns sort into three families based on what they organize:

FamilyConcerned withExamples
CreationalHow objects get createdFactory Method, Builder, Singleton, Abstract Factory
StructuralHow classes/objects are composedAdapter, Decorator, Facade
BehaviouralHow objects communicateObserver, Strategy, Command

Everything in this lesson lives in the Creational box. And every one of them is really an application of a single principle, so let's meet that first.

The principle beneath everything: Encapsulate What Varies#

Find the part of your code that changes, and separate it from the part that stays the same.

Analogy — the restaurant kitchen. The dining room never changes: tables, waiters, the act of ordering. What varies is the menu. A well-run restaurant keeps the menu on a separate card, not carved into the tables. Swap the card, keep the room. In code, you push the volatile part (which compiler? which fields? how many instances?) behind a stable interface so the rest of your program never has to be edited when it changes.

Why it matters This is the load-bearing idea. It's been called "the basis of almost every design pattern," and it's closely tied to the SOLID principles. If you understand only one thing here, understand that Factory, Builder, and Singleton are three different answers to "which part varies, and how do I wall it off?"

§1Factory Method#

Create an object without forcing the caller to name the exact class being created.

Analogy — ordering coffee. You walk up to the counter and say "a latte, please." You don't reach behind the bar, grab the specific machine, and operate it yourself. The barista (the factory) hears your request and produces the right drink. You care about the result — a latte — not about which machine made it. Factory Method puts a barista between your code and the messy business of construction.

The scenario that motivates it#

Design a compiler for LeetCode that handles Java, C++, and Python. The public API is fixed:

class LeetcodeCompiler {
    public void compile(String code, String language) { }
}

The naïve instinct is to stuff a giant if/else or switch right inside compile(), mixing "figure out which compiler" with "actually run the compiler." Every new language then means editing this one method — which does two unrelated jobs at once. The language choice is what varies. Encapsulate it.

The solution: pull creation into a Factory#

// Product interface — the stable contract
public interface Compilation {
    void compile(String code);
}

// The factory — the ONLY place that knows concrete classes
class CompilationFactory {
    public static Compilation getInstance(String language) {
        Compilation op = null;
        switch (language) {
            case "JAVA":   op = new JavaCompiler();   break;
            case "PYTHON": op = new PythonCompiler(); break;
            case "CPP":    op = new CPPCompiler();    break;
        }
        return op;
    }
}

// Concrete products
class JavaCompiler implements Compilation {
    public void compile(String code) { /* ... */ }
}
// CppCompiler, PythonCompiler ... same shape

// The client — blissfully ignorant of concrete classes
class LeetCodeCompiler {
    public void compile(String code, String language) {
        Compilation instance = CompilationFactory.getInstance(language);
        instance.compile(code);
    }
}
Client LeetCodeCompiler Factory CompilationFactory «interface» Compilation JavaCompiler CppCompiler PythonCompiler asks creates implements
The client asks the factory; the factory creates one of the concrete products; every product honors the same interface.

What this buys you#

  • Effortless creation of the right compiler object from a simple string.
  • Total abstraction — the client never imports or names JavaCompiler.
  • Loose coupling through the interface: swap implementations freely.
  • Scalable — adding a language touches the factory, not the client.
⚠ Pitfall "Scalable" has an asterisk. Adding a new language still means editing the factory's switch statement. So the factory itself is not closed to modification — it's a chokepoint you keep reopening. That's the honest trade: you've localized the change to one place instead of scattering it, but you haven't eliminated it. This is exactly why heavier variants like Abstract Factory exist.
💡 Interview tip Be precise with names. "Factory Method" and "Simple Factory" are related but distinct — the code above (a static method with a switch) is technically the Simple Factory idiom. True Factory Method defers creation to subclasses via an overridden method. Interviewers love catching people who blur these. +

§2Builder#

Construct a complex object step by step, keeping it readable and immutable, instead of drowning in constructor arguments.

Analogy — the sandwich shop. At Subway you don't shout your entire order in one breath. You walk the line: pick the bread, add cheese, add veggies, skip the onions, choose a sauce. Each choice is a small step, in any order, and only the ones you want. At the end, it's wrapped — sealed, done, unchangeable. That's a Builder: a construction line for objects.

The scenario: a Student Enrollment Form#

The form has two mandatory fields (first name, last name) and many optional ones (age, gender, isGraduate, hasExperience, city, state, isEarning…). Once filled, details cannot be changed — so every field is private final (that's what makes the object immutable). Now: how do we let callers set only the fields they care about, while keeping immutability? We try three approaches; watch two of them fail.

Failed attempt #1 — Telescoping constructors#

Write one constructor per combination, each calling the next:

public EnrollmentForm(String firstName, String lastName) {
    this(firstName, lastName, 0);
}
public EnrollmentForm(String firstName, String lastName, int age) {
    this(firstName, lastName, age, null);
}
// ... and on, and on ...

This works for a handful of fields, but it can't express "first name, last name, and gender" without yet another constructor — and with 10 optional fields you'd need an unmanageable number of overloads. Not scalable, and unreadable at the call site.

Failed attempt #2 — JavaBean (setters)#

Use a small constructor for mandatory fields, then setters for the rest:

public void setAge(int age) { this.age = age; }
public void setGender(String gender) { this.gender = gender; }

Flexible, yes — but a setter, by definition, lets you change a field after creation. That destroys immutability: someone can call form.setGender(...) tomorrow. Requirement violated.

⭐ The trick that makes it click Look at what varies here: the set of optional fields being supplied changes with every caller; the mandatory fields and the final object never do. So encapsulate the varying part into a separate helper object — the Builder. The Builder is free to be mutable while it collects your choices; the real object it produces is frozen. Mutability lives in the builder, immutability lives in the product. That split is the entire pattern.

The solution — a static inner Builder class#

public class EnrollmentForm {
    private final String firstName;
    private final String lastName;
    private final int age;
    private final String gender;

    // private: only the Builder may construct us
    private EnrollmentForm(Builder builder) {
        this.firstName = builder.firstName;
        this.lastName  = builder.lastName;
        this.age       = builder.age;
        this.gender    = builder.gender;
    }

    public static class Builder {
        private final String firstName;  // mandatory
        private final String lastName;
        private int age;                  // optional
        private String gender;

        public Builder(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
        public Builder age(int age)     { this.age = age;    return this; }
        public Builder gender(String g)  { this.gender = g;  return this; }

        public EnrollmentForm build() {
            return new EnrollmentForm(this);
        }
    }
}

And the call site reads like a sentence:

EnrollmentForm form = new EnrollmentForm.Builder("John", "Doe").age(25).gender("Male").build();
// EnrollmentForm{firstName='John', lastName='Doe', age=25, gender='Male'}
new Builder (mandatory) .age(25) .gender(..) .build() FROZEN each step returns the builder → chain continues build() → immutable object
Every optional setter returns this, so calls chain fluently until .build() seals an immutable object.

Run through the requirements and it passes all three: create with just the two names? Yes. Create with names + gender only? Yes. Change gender afterward? No — there's no setter on the finished object, only on the builder. That last "no" is the win.

When to reach for Builder#

ApproachFlexible?Immutable?Scales to many fields?
Telescoping ctorsPartlyYesNo — combinatorial blowup
JavaBean settersYesNo — mutableYes
BuilderYesYesYes

Rule of thumb: reach for Builder when an object needs more than 4–5 fields to construct. Its three advantages are maintainability (readable at the call site), robustness (the client only ever receives a fully-constructed object), and immutability.

💡 Lombok shortcut Writing a Builder by hand is a lot of boilerplate. In Java, the Lombok library generates it for you — annotate the class with @Builder (and @Getter) and the entire inner Builder class is produced at compile time. You get EnrollmentForm.builder().firstName("John").build() for free.
@Getter @Builder
public class EnrollmentForm {
    private final String firstName;
    private final String lastName;
    private final int age;
    // ... rest of the fields, no boilerplate
}
Why it matters The Builder is the textbook demonstration of "Encapsulate What Varies" applied to construction. The formal definition: it separates the construction of a complex object from its representation, so the same construction process can produce different representations. In interviews, connect it to immutability and thread-safety — an immutable object is inherently thread-safe, which is a big reason Builder shows up in real codebases.

§3Singleton#

Guarantee a class has exactly one instance, and give the whole program a single shared access point to it.

Analogy — the country's president. No matter who asks, at any given moment there is exactly one sitting president, and everyone refers to the same one. You can't spin up a second president for convenience. A Singleton is that: one instance, globally reachable, no duplicates allowed.

The scenario: a Logger#

A logger records messages to a log file. The catch: the whole application must write to a single log file. If two logger objects each open the file, you get access conflicts and inconsistent output. So we need to forbid creating more than one Logger. How?

Building it up, one obstacle at a time#

We derive the pattern by hitting walls:

  • Problem: as long as the class has a public constructor, anyone can write new Logger() as many times as they like. Fix: make the constructor private.
  • New problem: now nobody can create even one instance. Fix: a private constructor is still callable from inside the class — so add a getInstance() method that lives inside the class and does the creating.
  • New problem: we can't call getInstance() without an object, which we can't make. Fix: make getInstance() static so it belongs to the class, not an instance.
  • New problem: callers can still invoke getInstance() repeatedly and get multiple instances. Fix: keep a static reference; only create if it's null, otherwise return the one that exists.
public class Logger {
    private static Logger instance;    // the one and only

    private Logger() { }                 // 1. no public construction

    public static Logger getInstance() { // 2. static access point
        if (instance == null) {         // 3. create once, reuse forever
            instance = new Logger();
        }
        return instance;
    }
}
Client · req 1 Client · req 2 Client · req 3 Logger the ONE instance getInstance()
Every request routes through getInstance() and lands on the same single object — no matter how many callers.

The recipe in three steps#

  1. Make the constructor private.
  2. Create a static method to hand out the instance.
  3. Inside it, check if the instance already exists — return it if so, otherwise create a new one.
⚠ Pitfall — the naïve version is not thread-safe The if (instance == null) check above breaks under concurrency. Two threads can both evaluate the condition as true before either assigns, and you end up with two Loggers — exactly the thing you were preventing. This is the single most common Singleton interview follow-up. +
💡 Thread-safe fixes to know Four standard remedies, roughly in order of preference: (a) eager initialization — create the instance at class-load time (private static final Logger instance = new Logger();), simple and safe; (b) synchronized getInstance() — correct but slower on every call; (c) double-checked locking with a volatile field — fast and safe but fiddly; (d) an enum singleton — Joshua Bloch's recommended approach, thread-safe and serialization-proof by construction. +

Why Singleton is worth it (for a Logger)#

  • Centralized control over a shared resource or configuration.
  • Resource management — one file handle / connection, created and cleaned up once, no conflicts.
  • Consistency — every part of the app logs with the same config and log level, so output is uniform.
  • Lower memory — one object with one set of handlers, shared everywhere, instead of many.
Why it matters Singleton is the most controversial creational pattern — critics call it a "global variable in disguise" that makes code hard to test and hides dependencies. Know both sides: it's genuinely useful for loggers, config, and connection pools, but overusing it couples everything to a global and complicates unit testing (you can't easily swap in a mock). Mentioning this nuance signals maturity in an interview.

How the three fit together#

All three are creational, all three are "Encapsulate What Varies" wearing different clothes — but they answer different questions:

PatternQuestion it answersWhat varies (and gets hidden)Signature move
Factory MethodWhich class do I instantiate?The concrete type chosen at runtimeAsk a factory instead of calling new
BuilderHow do I assemble a complex object?Which optional fields get set, and in what orderFluent chain → .build()
SingletonHow many instances may exist?The instantiation count (forced to one)Private ctor + static getInstance()

Interview & Practice Q&A#

Tap a question to reveal a 2–4 sentence answer you can read aloud.

What problem do creational design patterns solve, in one sentence?
They handle the question of how objects are created, so that construction logic doesn't tightly couple your code to concrete classes, bury you in constructor arguments, or spawn instances you didn't intend. Each one localizes a specific "what varies" about object creation behind a stable interface.
How is a Factory different from just calling new?
Calling new JavaCompiler() hard-codes the concrete class into the caller, so any change means editing the caller. A factory moves that decision into one place: the client asks for a Compilation by name and gets back the right implementation, never naming the concrete class. That gives loose coupling and a single point to extend.
The Factory still has a switch statement — isn't that a code smell?
It's a real limitation: adding a type means reopening the factory, so it isn't fully closed to modification. The value is that the change is confined to one location instead of scattered across every caller. When even that becomes painful, you graduate to patterns like Abstract Factory or a registry/map of suppliers.
Why not just use telescoping constructors or setters instead of a Builder?
Telescoping constructors explode combinatorially — you can't express arbitrary subsets of optional fields without writing a constructor for each, and the call site becomes unreadable. Setters (the JavaBean pattern) fix flexibility but break immutability, since anything with a setter can be mutated after creation. Builder gives you both flexibility and immutability at once.
What single trick makes the Builder's fluent chain work?
Each optional setter returns this — the builder itself — so you can immediately call the next setter on the result. That chaining is what produces the readable .age(25).gender("Male").build() syntax. The mutable builder collects your choices; build() then produces a frozen, immutable object.
When should you actually reach for the Builder pattern?
When an object needs more than about 4–5 fields to construct, especially when several are optional and the object should be immutable. It buys maintainability, robustness (clients only ever see a fully-built object), and immutability. If it's just one or two fields, a plain constructor is fine.
Walk me through implementing a Singleton.
Three steps: make the constructor private so nobody can call new; add a static getInstance() method as the single access point; and inside it, keep a static reference — create the instance only if it's null, otherwise return the existing one. That guarantees one instance, lazily created, reachable everywhere.
Why is the basic Singleton not thread-safe, and how do you fix it?
Two threads can both see instance == null as true before either assigns, so each creates its own instance — defeating the purpose. Fixes include eager initialization (build it at class-load time), a synchronized getInstance, double-checked locking with a volatile field, or an enum singleton, which is thread-safe and serialization-safe by design.
Why are the Enrollment Form's fields declared final?
Because the requirement says details cannot change once filled — the object must be immutable. Declaring every field final enforces that at the language level: they can be assigned exactly once, in the constructor, and never reassigned. Immutability also makes the object inherently thread-safe.
What's the one principle connecting all three patterns?
"Encapsulate what varies" — identify the part of the design that changes and separate it from the part that stays constant. Factory hides which concrete class varies, Builder hides which optional fields vary, and Singleton controls how the instance count varies. It's described as the basis of almost every design pattern.

Key Takeaways#

  1. Encapsulate what varies is the root idea — every pattern here is one specific answer to "which part changes, and how do I wall it off?"
  2. Factory Method hides which concrete class you get. Client asks a factory instead of calling new → loose coupling, easy to extend. Watch the switch: change is localized, not eliminated.
  3. Builder handles objects with many (≥4–5) fields, some optional, all immutable. The magic is setters returning this to chain, and a .build() that seals a frozen object. Lombok's @Builder removes the boilerplate.
  4. Singleton forces exactly one instance: private constructor + static getInstance() + null-check. The naïve version isn't thread-safe — know the fixes (eager init, synchronized, double-checked lock, enum).
  5. All three are Creational patterns; they answer which object, how to build it, and how many can exist, respectively.
A curated reference on Creational Design Patterns. Items tagged + are added context (e.g. thread-safety fixes, Simple-vs-Factory-Method distinction).