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.
Assumes you know
After this you should be able to
- Choose a creational pattern by problem
- Explain builder immutability and singleton risks
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.
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 ·Productinterface ·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.
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 constructorprivate, takes Builder. - Builder: mandatory in constructor, optional via chained setters that
return this. new Form.Builder("John","Doe").age(25).gender("Male").build();
| Path tried | Why it failed |
|---|---|
| Telescoping ctors | combinatorial 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.
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. +
| Pattern | One-liner | Answers |
|---|---|---|
| Factory | hide which class | WHICH object? |
| Builder | assemble step-wise | HOW to build? |
| Singleton | only one, ever | HOW MANY? |
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:
| Family | Concerned with | Examples |
|---|---|---|
| Creational | How objects get created | Factory Method, Builder, Singleton, Abstract Factory |
| Structural | How classes/objects are composed | Adapter, Decorator, Facade |
| Behavioural | How objects communicate | Observer, 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.
§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); } }
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.
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.
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 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'}
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#
| Approach | Flexible? | Immutable? | Scales to many fields? |
|---|---|---|---|
| Telescoping ctors | Partly | Yes | No — combinatorial blowup |
| JavaBean setters | Yes | No — mutable | Yes |
| Builder | Yes | Yes | Yes |
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.
@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 }
§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 constructorprivate. - 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: makegetInstance()staticso 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; } }
getInstance() and lands on the same
single object — no matter how many callers.The recipe in three steps#
- Make the constructor private.
- Create a static method to hand out the instance.
- Inside it, check if the instance already exists — return it if so, otherwise create a new one.
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. +
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.
How the three fit together#
All three are creational, all three are "Encapsulate What Varies" wearing different clothes — but they answer different questions:
| Pattern | Question it answers | What varies (and gets hidden) | Signature move |
|---|---|---|---|
| Factory Method | Which class do I instantiate? | The concrete type chosen at runtime | Ask a factory instead of calling new |
| Builder | How do I assemble a complex object? | Which optional fields get set, and in what order | Fluent chain → .build() |
| Singleton | How 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?
How is a Factory different from just calling new?
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?
Why not just use telescoping constructors or setters instead of a Builder?
What single trick makes the Builder's fluent chain work?
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?
Walk me through implementing a Singleton.
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?
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?
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?
Key Takeaways#
- 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?"
- Factory Method hides which concrete class you get. Client asks a factory
instead of calling
new→ loose coupling, easy to extend. Watch theswitch: change is localized, not eliminated. - Builder handles objects with many (≥4–5) fields, some optional, all immutable. The
magic is setters returning
thisto chain, and a.build()that seals a frozen object. Lombok's@Builderremoves the boilerplate. - 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). - All three are Creational patterns; they answer which object, how to build it, and how many can exist, respectively.