The short version
- Creational patterns answer one question: who decides which object gets made, and how? Each one moves that decision somewhere it can be controlled.
- Singleton promises one instance. The obvious lazy version breaks with threads (we measured up to 7 instances). Use the holder idiom or an enum, and usually prefer passing one object around instead.
- Factory Method and the simple factory keep “which concrete class?” in one place, so callers depend only on an interface.
- Abstract Factory creates whole families of objects that must match, so you can’t mix one provider’s charger with another’s webhook checker.
- Builder turns a constructor with ten arguments into named steps, and validates once before the object exists. Prototype copies a ready-made object, and the copy must be deep where it matters.
Before we start
A design pattern is a named solution to a problem that keeps coming up. The name is half the value: “I’ll put a factory here” says in five words what would otherwise take a paragraph, and an interviewer hears that you know the trade-offs that come with it.
The classic catalogue comes from a 1994 book, Design Patterns, by four authors often called the Gang of Four (GoF). It lists 23 patterns in three families:
| Family | The question it answers | Chapter |
|---|---|---|
| Creational | How do objects get created? | this one |
| Structural | How do objects fit together? | chapter 5 |
| Behavioral | How do objects share work and talk to each other? | chapter 6 |
Each pattern in this chapter follows the same shape: the problem (with the code you’d write without the pattern), the pattern (the idea and a diagram), the code (complete, compiled and run, with its real output), mistakes and variations, when not to use it, and where it shows up in the interview problems in this track.
One warning that applies to every pattern: a pattern is a tool for a problem you have, not a decoration. If you can’t say which problem a pattern is solving in your design, take it out.
Singleton: exactly one instance
The problem
Some things should exist once per program: the configuration loaded at startup, a pool of database connections, a cache that every part of the app shares. If two parts of the code each create their own connection pool, you get twice the connections the database allows, and settings that disagree.
The first version almost everyone writes:
class LazyConfig {
private static LazyConfig instance;
private LazyConfig() { /* read settings from disk */ }
static LazyConfig getInstance() {
if (instance == null) instance = new LazyConfig(); 1
return instance;
}
}- 1check, then create
It looks right: the constructor is private, so nobody else can call new, and getInstance creates the object the first time it’s asked for. That’s called lazy creation.
The bug is line (1): checking and creating are two steps. Two threads can both find instance == null before either has finished creating one. We tested it: 8 threads calling getInstance() at the same moment, repeated 2,000 times, with a constructor that takes a moment (as reading a settings file does):
naive lazy singleton, 2000 trials of 8 threads: more than one instance in 36 trials (worst: 7 instances)In 36 of 2,000 tries there was more than one “single” instance, and once there were seven. Rare enough to pass every test; common enough to happen on a busy server’s first second.
The pattern
Make the constructor private, and make creation itself happen exactly once, using something Java already guarantees.
The code
Three correct versions, from simplest to laziest.
// Version 1: created when the class loads. Java runs static initialisers once,
// on one thread, so this is safe with no locking.
final class EagerConfig {
private static final EagerConfig INSTANCE = new EagerConfig();
private EagerConfig() { }
static EagerConfig getInstance() { return INSTANCE; }
}
// Version 2: the holder idiom. Created on first use, still with no locking.
final class HolderConfig {
private final Map settings = new HashMap<>();
private HolderConfig() { settings.put("currency", "INR"); }
private static final class Holder { // (1)
static final HolderConfig INSTANCE = new HolderConfig();
}
static HolderConfig getInstance() { return Holder.INSTANCE; }
String get(String key) { return settings.get(key); }
}
// Version 3: an enum with one value. Also safe against copying by serialization.
enum EnumConfig {
INSTANCE;
private final Map settings = Map.of("currency", "INR");
String get(String key) { return settings.get(key); }
} - Java loads the nested
Holderclass only the first timegetInstance()touches it, and loading a class is already thread-safe. So you get lazy creation with no locks and no check-then-create gap.
A fourth version you’ll see in older code is double-checked locking: check, lock, check again, create. It works only if the field is volatile, and it’s easy to get wrong. The holder idiom does the same job with less to explain.
class SingletonDemo {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(8);
Set seen = ConcurrentHashMap.newKeySet();
List> fs = new ArrayList<>();
for (int i = 0; i < 10_000; i++)
fs.add(pool.submit(() -> seen.add(HolderConfig.getInstance())));
for (Future> f : fs) f.get();
pool.shutdown();
System.out.println("holder idiom, 10,000 calls on 8 threads: instances = "
+ seen.size());
System.out.println("enum: " + EnumConfig.INSTANCE.get("currency")
+ ", same object: "
+ (EnumConfig.INSTANCE == EnumConfig.valueOf("INSTANCE")));
}
} holder idiom, 10,000 calls on 8 threads: instances = 1
enum: INR, same object: trueMistakes and variations
- Lazy without safety, as above.
- A Singleton that holds changing state (a “current user”, a shopping cart). Now every part of the program shares one mutable object, which is the worst kind of global variable.
- Breaking it by accident. Reflection can call a private constructor, and serialization can create a copy when an object is read back. The enum version is immune to both; that’s why some people recommend it.
When not to use it
A Singleton is a global variable with a nicer name, and globals have two real costs.
Tests can’t replace it. If ParkingLot calls PaymentGateway.getInstance() inside its code, a test can’t hand it a fake gateway. Every test ends up talking to the same real object.
Dependencies are hidden. You can’t tell from ParkingLot‘s constructor that it needs a gateway; you have to read every method.
The usual better answer is dependency injection: create one object at startup and pass it to whoever needs it. There’s still only one, but nothing in the code is forced to assume that.
// Instead of reaching for a global inside the class...
// PaymentGateway gw = PaymentGateway.getInstance();
// ...ask for it, and let main() decide which one.
// ParkingLot lot = new ParkingLot(floors, pricing, new RazorpayGateway(keys));Keep Singleton for things that are genuinely one-per-process and stateless or read-only: configuration loaded at startup, a logger.
Where it shows up
Every problem walkthrough in this track discusses “why not a Singleton”, starting with the parking lot. The answer is the same each time: tests need fresh instances with fake clocks and gateways, and “only one” stops being true sooner than you expect.
Interviewer: Make the parking lot a Singleton, there’s only one lot.
You: I can, and I’d use the holder idiom so it’s thread-safe. But I’d rather create one lot at startup and pass it to the gates. There’s still one, but tests can build a lot with exactly 50 spots and a fake clock, and when the company opens a second mall, nothing has to change.
Factory Method: decide which class, in one place
The problem
A notification service sends messages by SMS, email or push. Without a pattern, every place that sends something decides which class to create:
Notifier n;
if (channel.equals("SMS")) n = new SmsNotifier(smsClient);
else if (channel.equals("EMAIL")) n = new EmailNotifier(mailer);
else n = new PushNotifier(pushClient);
n.send(user, text);Copy that into five places and adding WhatsApp means finding and editing all five. Miss one and WhatsApp messages quietly go out as push notifications from that one screen.
The pattern
Put the decision in one place, and let everyone else depend only on the interface.
There are two versions, and it helps to know both names.
Simple factory: one class with a method that returns the right object. Not one of the original 23, but the most common in practice.
Factory Method (the GoF pattern): a base class does some work that needs an object, and subclasses decide which object, by overriding one “factory method”. The base class’s steps stay the same.
The code
The simple factory, for notifications:
enum Channel { SMS, EMAIL, PUSH }
interface Notifier { String send(String user, String text); }
final class SmsNotifier implements Notifier {
public String send(String user, String text) {
return "SMS to " + user + ": " + text;
}
}
final class EmailNotifier implements Notifier {
public String send(String user, String text) {
return "Email to " + user + ": " + text;
}
}
final class PushNotifier implements Notifier {
public String send(String user, String text) {
return "Push to " + user + ": " + text;
}
}
final class NotifierFactory {
Notifier create(Channel channel) {
return switch (channel) { // (1)
case SMS -> new SmsNotifier();
case EMAIL -> new EmailNotifier();
case PUSH -> new PushNotifier();
};
}
}- The only place in the program that names the concrete classes. A
switchover an enum also has a bonus in Java: addWHATSAPPto the enum and the compiler refuses to build until thisswitchhandles it.
And Factory Method, for exporting a report in different formats:
interface Formatter {
String header();
String row(Map r);
}
abstract class ReportExporter {
String export(List - 2the fixed steps
- 3the factory method
- The export steps (header, then one line per row) are written once.
- Each subclass only answers “which formatter?”. That’s the whole pattern.
SMS to asha: Order on its way
Email to asha: Order on its way
Push to asha: Order on its way
city,orders
Pune,120
Kochi,85
{"city":"Pune","orders":120}
{"city":"Kochi","orders":85}Mistakes and variations
- *A factory that just wraps
new* with no decision in it adds a class and nothing else. - Passing strings (
create("sms")) instead of an enum means typos fail at runtime instead of compile time. - Static factory methods like
List.of(...),Duration.ofMinutes(5)orGame.classic(x, o)in tic-tac-toe are a lighter cousin: a named way to create an object, often returning a cached or more specific one. - Factory Method is closely related to Template Method (chapter 6): a fixed algorithm in the base class, with one step left to subclasses. Factory Method is the special case where that step creates an object.
When not to use it
When there’s only one class and no real prospect of a second. new ParkingFloor(level) is perfectly clear.
Where it shows up
In the rate limiter, Algorithm.newState(limit, now) is a factory method: each algorithm decides which kind of per-key state to create, and the limiter never knows. That design is what made the combined AllOf limit possible as a new class.
Abstract Factory: families that must match
The problem
A shop takes payments through one of two providers. Each provider needs three pieces that only work with each other: something that charges a card, something that refunds, and something that checks that a webhook really came from that provider. If the code creates them separately, one config typo pairs provider A’s charger with provider B’s webhook checker. Charges go through, but every “payment succeeded” webhook is rejected as forged, and orders are never confirmed.
The pattern
One interface that creates the whole family. Each provider gets one implementation, which can only produce pieces that belong together. Code picks the family once, at startup.
The code
The providers here are simplified stand-ins, not real client code.
interface Charger { String charge(long paise); }
interface WebhookVerifier { boolean verify(String body, String signature); }
interface PaymentKit { // (1)
Charger charger();
WebhookVerifier webhooks();
}
final class RazorpayKit implements PaymentKit {
private final String secret;
RazorpayKit(String secret) { this.secret = secret; }
public Charger charger() { return paise -> "rzp_pay_" + paise; }
public WebhookVerifier webhooks() {
return (body, sig) -> sig.equals("rzp:" + (body + secret).hashCode());
}
}
final class StripeKit implements PaymentKit {
private final String secret;
StripeKit(String secret) { this.secret = secret; }
public Charger charger() { return paise -> "pi_" + paise; }
public WebhookVerifier webhooks() {
return (body, sig) -> sig.equals("t=1,v1=" + (secret + body).hashCode());
}
}
final class Checkout {
private final Charger charger;
private final WebhookVerifier webhooks;
Checkout(PaymentKit kit) { // (2)
this.charger = kit.charger();
this.webhooks = kit.webhooks();
}
String pay(long paise) { return charger.charge(paise); }
boolean confirm(String body, String sig) { return webhooks.verify(body, sig); }
}
class AbstractFactoryDemo {
public static void main(String[] args) {
PaymentKit kit = new RazorpayKit("s3cret"); 3
Checkout checkout = new Checkout(kit);
String body = "{\"event\":\"paid\"}";
System.out.println("charged: " + checkout.pay(49_900));
System.out.println("real webhook accepted: "
+ checkout.confirm(body, "rzp:" + (body + "s3cret").hashCode()));
System.out.println("other provider's signature accepted: "
+ checkout.confirm(body, "t=1,v1=" + ("s3cret" + body).hashCode()));
}
}- 3chosen once
- One interface for the whole family.
Checkoutreceives one kit and takes both pieces from it. It can’t end up with a mismatched pair.- The family is chosen in exactly one line.
charged: rzp_pay_49900
real webhook accepted: true
other provider's signature accepted: falseMistakes and variations
- Adding a new kind of product is expensive. If every provider must now also create a
PayoutSender, the interface and every kit change. Abstract Factory makes adding a new family easy and adding a new product hard. Say that trade-off out loud. - The classic textbook example is UI themes: a dark-theme factory makes dark buttons, dark menus and dark dialogs, so a screen can never mix themes.
When not to use it
When objects don’t actually need to match. If any notifier works with any template, two separate simple factories are simpler.
Where it shows up
Any time a design swaps a whole backend: an in-memory set of repositories for tests and a database-backed set for production is an abstract factory, even if nobody calls it that.
Builder: many settings, built safely
The problem
A food-delivery order has a customer, a restaurant, items, and a handful of optional extras: a coupon, delivery instructions, contactless delivery, a scheduled time, a tip. The constructor ends up like this:
new FoodOrder("u42", "r7", items, null, "HAPPY10", true, null, 2000, false);Which null is which? Is true contactless or “leave at door”? Swap two String arguments and it still compiles. And there’s no single place to check rules like “a scheduled order must be at least 30 minutes ahead”.
The pattern
A separate builder object collects settings through named methods, one at a time, then checks everything in build() and creates the final, unchangeable object. Until build() succeeds, there is no order.
The code
final class FoodOrder {
private final String customer, restaurant, coupon;
private final List items;
private final boolean contactless;
private final long tipPaise;
private FoodOrder(Builder b) { // (1)
this.customer = b.customer;
this.restaurant = b.restaurant;
this.items = List.copyOf(b.items);
this.coupon = b.coupon;
this.contactless = b.contactless;
this.tipPaise = b.tipPaise;
}
static Builder builder(String customer, String restaurant) { // (2)
return new Builder(customer, restaurant);
}
@Override public String toString() {
return "Order[" + customer + " from " + restaurant + ": " + items
+ (coupon == null ? "" : ", coupon " + coupon)
+ (contactless ? ", contactless" : "")
+ (tipPaise > 0 ? ", tip ₹" + tipPaise / 100 : "") + "]";
}
static final class Builder {
private final String customer, restaurant;
private final List items = new ArrayList<>();
private String coupon;
private boolean contactless;
private long tipPaise;
private Builder(String customer, String restaurant) {
this.customer = Objects.requireNonNull(customer);
this.restaurant = Objects.requireNonNull(restaurant);
}
Builder item(String name) { items.add(name); return this; } // (3)
Builder coupon(String code) { this.coupon = code; return this; }
Builder contactless() { this.contactless = true; return this; }
Builder tip(long paise) { this.tipPaise = paise; return this; }
FoodOrder build() { // (4)
if (items.isEmpty())
throw new IllegalStateException("an order needs an item");
if (tipPaise < 0) throw new IllegalStateException("tip can't be negative");
return new FoodOrder(this);
}
}
}
class BuilderDemo {
public static void main(String[] args) {
FoodOrder o = FoodOrder.builder("asha", "Meghana Foods")
.item("Chicken biryani").item("Raita")
.coupon("HAPPY10")
.contactless()
.tip(30_00)
.build();
System.out.println(o);
try {
FoodOrder.builder("ravi", "Meghana Foods").tip(20_00).build();
} catch (IllegalStateException e) {
System.out.println("refused: " + e.getMessage());
}
}
} - The private constructor means the only way to get a
FoodOrderis throughbuild(), so every order that exists has passed the checks. - Required values go in the first call; optional ones are named methods. You can’t forget a required one.
- Each method returns the builder, so calls chain and read like a sentence.
- All the rules in one place, run once, before the object exists.
Order[asha from Meghana Foods: [Chicken biryani, Raita], coupon HAPPY10, contactless, tip ₹30]
refused: an order needs an itemMistakes and variations
- A builder for a class with three fields. A constructor or a record is clearer.
- Not copying collections in the final object: if
FoodOrderkept the builder’s list, changing the builder later would change an order that was already placed.List.copyOfprevents that. - Builders for tests. A test builder with sensible defaults (
anOrder().withCoupon("X").build()) makes tests short and readable even when the real object has many fields. - Java libraries use builders everywhere:
HttpRequest.newBuilder(),StringBuilder,Stream.builder().
When not to use it
When the object has a few fields, all required. new Seat("B2", SeatType.PREMIUM) needs no builder.
Where it shows up
In the parking lot, the constructor takes five arguments, and the walkthrough deliberately says “with ten optional settings, a builder would earn its place”. Knowing when not to use it is part of the answer.
Prototype: copy a ready-made object
The problem
An events app lets organisers create a new event from a template: a “Saturday comedy night” with a venue, a seat map, prices and a checklist of tasks. Building one from scratch takes dozens of calls. Copying an existing, fully set-up one would be easier. The catch is in what “copy” means.
The pattern
Give objects a way to copy themselves. New objects start as copies of a configured prototype, then get the few changes that make them different.
The code
Here are two copy methods: the easy one, and the right one.
final class EventTemplate {
String name;
List checklist;
EventTemplate(String name, List checklist) {
this.name = name;
this.checklist = checklist;
}
EventTemplate shallowCopy() { // (1)
return new EventTemplate(name, checklist);
}
EventTemplate copy() { // (2)
return new EventTemplate(name, new ArrayList<>(checklist));
}
}
class PrototypeDemo {
public static void main(String[] args) {
EventTemplate comedy = new EventTemplate("Comedy night",
new ArrayList<>(List.of("Book venue", "Confirm acts")));
EventTemplate a = comedy.shallowCopy();
a.name = "Comedy night, 14 Nov";
a.checklist.add("Print tickets for 14 Nov");
System.out.println("shallow: template checklist = " + comedy.checklist);
EventTemplate fresh = new EventTemplate("Comedy night",
new ArrayList<>(List.of("Book venue", "Confirm acts")));
EventTemplate b = fresh.copy();
b.checklist.add("Print tickets for 21 Nov");
System.out.println("deep: template checklist = " + fresh.checklist);
System.out.println("deep: copy checklist = " + b.checklist);
}
} - A shallow copy copies the fields, but
checklistis a reference, so both objects point at the same list. - A deep copy makes a new list. Changes to the copy stay in the copy.
shallow: template checklist = [Book venue, Confirm acts, Print tickets for 14 Nov]
deep: template checklist = [Book venue, Confirm acts]
deep: copy checklist = [Book venue, Confirm acts, Print tickets for 21 Nov]The shallow copy quietly changed the template itself: every future comedy night now has “print tickets for 14 Nov” on its checklist. This is the classic Prototype bug.
Mistakes and variations
- *Java’s
clone()andCloneable.* The built-in mechanism is shallow by default, skips constructors, and has awkward rules. Most modern Java code writes a copy constructor or acopy()method instead, as above. - Immutable objects don’t need copying at all. If
EventTemplatewere a record with an unmodifiable list, sharing would be safe, and “change one field” would be a method that returns a new object. That’s often the better design.
When not to use it
When creating from scratch is easy, or when objects are immutable and can simply be shared.
Where it shows up
In the snakes and ladders walkthrough, the board is immutable precisely so a million simulated games can share one board with no copying at all. And in chess, the “give each reader its own copy of the board” advice in step 10 is Prototype with a deep copy.
Choosing between them
| You need | Use | Watch out for |
|---|---|---|
| Exactly one shared, read-only object | Singleton (holder or enum), or better, one object passed in | Lazy creation without thread safety; hidden dependencies |
| Callers shouldn’t pick the concrete class | Simple factory or Factory Method | A factory with no real decision in it |
| Objects that must come in matching sets | Abstract Factory | Adding a new kind of product changes every family |
| An object with many optional settings and rules | Builder | Using it for small objects |
| New objects that start as copies of configured ones | Prototype | Shallow copies sharing lists and maps |
How this shows up in interviews
Interviewers rarely ask “explain Builder”. They ask you to design something, and watch whether creation is handled sensibly. Three moments come up again and again:
- ”Should this be a Singleton?” Give the thread-safe version, then argue for passing one instance in, with the testing reason. That answer appears in every problem walkthrough in this track.
- ”How would you add a new type?” If the answer is “one new class and one line in the factory”, that’s the factory paying for itself.
- ”This constructor has a lot of parameters.” Offer a builder, and validate in
build().
Interviewer: Your Notification has a recipient, a channel, a template, a priority, a send time, a retry policy and a deduplication key. How would you create one?
You: Recipient and channel are required, so they’d go in the builder’s first call. The rest are optional named methods, with sensible defaults. build() would check the rules, like “a scheduled time must be in the future”, so an invalid notification can never exist. And I’d make the final object immutable, because notifications get handed between threads in a queue.
Stay in the loop
Get new problems and walkthroughs by email
One short email when something new is published: a deep-dive walkthrough, an LLD problem, or a real outage retold. No spam, unsubscribe any time.