Sitemap

Creational design patterns 101

8 min readMay 1, 2025

--

I am always confused when I see design patterns on how to remember them. How to know which design pattern I should be using now depending on a scenario. Also I tend to forget about patterns as well. I am writing this one blog to cover all creational design pattern in one go for the younger me, who never will need to get confused on when to use what design pattern.

Creational design pattern : Simple, a design pattern to create new object. Whenever its about creating new object you can come to this list and take one of creational design pattern according to your use case.

Below are all creational design pattern

  1. Singleton

2. Factory

3. Abstract Factory

4. Builder

5. Prototype

1. Singleton

Trigger Word: create “Only one”

Analogy: Your house has one Wi-Fi router. Every device (phone, laptop) connects to it. You don’t buy a new router for each device , that’d be chaos and expensive! Similarly, a Singleton ensures one instance of a class (like a DB connection) is shared across the app.

Real-World App Example:

  • Slack: One NotificationService instance sends alerts to all users, reusing the same API connection.

Code “Click” Moment:

  • Scenario: Your app handles 1000 users, and every user request creates a new DB connection. The server crashes with “Too many connections.” Using Singleton for a DataSource creates one connection pool that everyone shares, keeping the app stable
  • Click: When you see the app running smoothly with thousands of users and only 10 DB connections, you realize, “Man, Singleton saved me from a production outage!”

Why Use It?: Ensures one instance for resource-heavy objects (like DB pools) to avoid duplication and conflicts.

Code:

public class Logger {
private static Logger instance;

private Logger() {} // Private constructor

public static Logger getInstance() {
if (instance == null) {
instance = new Logger();
}
return instance;
}

public void log(String message) {
System.out.println("Log: " + message);
}
}

// Usage
public class Main {
public static void main(String[] args) {
Logger logger1 = Logger.getInstance();
Logger logger2 = Logger.getInstance();
logger1.log("Hello"); // Log: Hello
System.out.println(logger1 == logger2); // true (same instance)
}
}

2. Factory

Trigger Word: “Make and give”

Analogy: You order a burger at a fast-food joint. You say, “Gimme a cheeseburger Jumbo,” and the kitchen decides which type to make (veggie,, chicken). You don’t care how it’s made, just that you get the right burger. A Factory Method decides which object to create based on input.

Real-World App Example:

  • Uber: You request a ride, and a VehicleFactory decides whether to give you a Car, Bike, or Auto based on availability.

Code “Click” Moment:

  • Scenario: Your app supports different burger types, and the client says, “Add a fish burger.” With a Factory, you add one if condition (or a new @Bean in Spring), and the new burger is live without touching the core logic.
  • Click: When you deploy the fish burger feature in 10 minutes and the client’s impressed, you think, “Yo, Factory Method made this so extensible!”
  • Why Use It?: Decides which object to create at runtime, keeping code flexible for new types without changing existing logic.
public interface Burger {
void serve();
}

public class CheeseBurger implements Burger {
public void serve() { System.out.println("Serving CheeseBurger"); }
}

public class VeggieBurger implements Burger {
public void serve() { System.out.println("Serving VeggieBurger"); }
}

public class BurgerFactory {
public Burger createBurger(String type) {
if (type.equalsIgnoreCase("cheese")) {
return new CheeseBurger();
} else if (type.equalsIgnoreCase("veggie")) {
return new VeggieBurger();
}
throw new IllegalArgumentException("Unknown burger type");
}
}

// Usage
public class Main {
public static void main(String[] args) {
BurgerFactory factory = new BurgerFactory();
Burger burger = factory.createBurger("cheese");
burger.serve(); // Serving CheeseBurger
}
}

3. Abstract Factory

Trigger Word: “Give me a set”

Analogy: You’re buying a gaming setup. You say, “I need a full PC gaming kit,” and the store gives you a matched set: gaming keyboard, mouse, and monitor. For a console setup, you get a controller, headset, and TV. An Abstract Factory provides a family of related objects.

Real-World App Example:

  • Amazon: A payment gateway set for a country (UPI, Card, Wallet) is created together to ensure compatibility.

Code “Click” Moment:

  • Scenario: Your app supports gaming and office setups. The client says, “Add a console setup.” With Abstract Factory, you create a new factory class for console components, and the app seamlessly uses the new set without breaking existing code.
  • Click: When you switch to a console setup with one config change and everything works perfectly, you realize, “Dude, Abstract Factory made managing related objects a breeze!”
  • Why Use It?: Provides a consistent set of related objects (like a full setup) while allowing easy swapping of entire families.
public interface Keyboard { void type(); }
public interface Mouse { void click(); }

public class GamingKeyboard implements Keyboard { public void type() { System.out.println("Gaming Keyboard"); } }
public class GamingMouse implements Mouse { public void click() { System.out.println("Gaming Mouse"); } }
public class OfficeKeyboard implements Keyboard { public void type() { System.out.println("Office Keyboard"); } }
public class OfficeMouse implements Mouse { public void click() { System.out.println("Office Mouse"); } }

public interface SetupFactory {
Keyboard createKeyboard();
Mouse createMouse();
}

public class GamingSetupFactory implements SetupFactory {
public Keyboard createKeyboard() { return new GamingKeyboard(); }
public Mouse createMouse() { return new GamingMouse(); }
}

public class OfficeSetupFactory implements SetupFactory {
public Keyboard createKeyboard() { return new OfficeKeyboard(); }
public Mouse createMouse() { return new OfficeMouse(); }
}

// Usage
public class Main {
public static void main(String[] args) {
SetupFactory factory = new GamingSetupFactory();
Keyboard keyboard = factory.createKeyboard();
Mouse mouse = factory.createMouse();
keyboard.type(); // Gaming Keyboard
mouse.click(); // Gaming Mouse
}
}

4. Builder

Trigger Word: “Build step-by-step”

Analogy: Ordering a custom PC — pick the CPU, add RAM, choose a GPU, select storage, step-by-step. You don’t get a prebuilt PC with fixed specs; you customize it. Builder lets you construct complex objects incrementally.

Real-World App Example:

  • Dell Website: You customize a laptop by selecting processor, RAM, and storage step-by-step.

Code “Click” Moment:

  • Scenario: Your app lets users build custom PCs. If you used a constructor with 10 parameters, it’d be a nightmare to maintain (and users might forget fields). Builder lets you add components flexibly, with optional fields and validation.
  • Click: When you add a new component (like SSD) to the Builder without changing existing code and users love the flexibility, you think, “Man, Builder made customization so clean!”
  • Why Use It?: Simplifies creating complex objects with many optional fields, keeping code readable and flexible.
public class Computer {
private String cpu;
private String ram;
private String gpu;

private Computer(Builder builder) {
this.cpu = builder.cpu;
this.ram = builder.ram;
this.gpu = builder.gpu;
}

public static class Builder {
private String cpu;
private String ram;
private String gpu;

public Builder cpu(String cpu) { this.cpu = cpu; return this; }
public Builder ram(String ram) { this.ram = ram; return this; }
public Builder gpu(String gpu) { this.gpu = gpu; return this; }
public Computer build() { return new Computer(this); }
}
}

// Usage
public class Main {
public static void main(String[] args) {
Computer computer = new Computer.Builder()
.cpu("Intel i9")
.ram("16GB")
.gpu("NVIDIA RTX 3080")
.build();
}
}

5. Prototype

Won’t start this quickly with trigger word, as I feel hard to find examples for this examples in code I used and wrote so far.

Little context for our prototype:

  • Prototype is about cloning an existing object to create a new one, instead of building it from scratch. It’s like making a photocopy of a template document and tweaking it for a specific use, so you don’t mess up the original or waste time recreating it.
  • Prototype helps by cloning a pre-initialized object instead of creating it from scratch every time, saving resources and time.

Scenario: Customizable Email Templates in a Marketing App. You’re building a marketing automation app . Your app lets users send personalized email campaigns to thousands of customers. Each email starts with a template (e.g., a “Welcome Email” with default subject, body, and banner image). Users can customize the template for each recipient (e.g., change the recipient’s name, add a custom offer).

Why is Object Creation Expensive?

The email template requires:

  • A DB call to fetch default content (subject, HTML body).
  • A file read to load a default banner image (e.g., from disk or S3).
  • These operations take milliseconds (10–100ms for DB, 5–50ms for file).
  • Creating a new EmailTemplate object for each recipient (e.g., 10,000 customers) means 10,000 DB calls + 10,000 file reads, which will:
  • i) Slow down the app (seconds of delay).
  • ii) Overload the DB and file system.
  • iii) Crash the server under load.

Why Prototype?

  • Instead of creating a new EmailTemplate from scratch for each recipient, you:
  • Load the template once (one DB call + one file read).
  • Clone the template for each recipient, applying their customizations.
  • Cloning is an in-memory operation (microseconds), saving tons of time and resources.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;

// EmailTemplate interface with clone method
public interface EmailTemplate extends Cloneable {
EmailTemplate clone();
void customize(String key, String value);
String getContent();
}

// Concrete EmailTemplate class
public class WelcomeEmailTemplate implements EmailTemplate {
private String subject;
private String body;
private byte[] bannerImage; // Default image
private Map<String, String> customizations;

// Expensive initialization (DB + file read)
public WelcomeEmailTemplate() {
// Simulate DB call to fetch defaults
try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/marketing", "user", "pass")) {
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT subject, body FROM email_templates WHERE type = 'welcome'");
if (rs.next()) {
this.subject = rs.getString("subject"); // e.g., "Welcome to Our Platform!"
this.body = rs.getString("body"); // e.g., "Hi [Name], thanks for joining!"
}
} catch (SQLException e) {
throw new RuntimeException("DB error");
}
// Simulate file read for banner image
try {
this.bannerImage = Files.readAllBytes(Paths.get("welcome_banner.png"));
} catch (IOException e) {
throw new RuntimeException("File read error");
}
this.customizations = new HashMap<>();
System.out.println("Loaded email template (DB + file)...");
}

// Customize the template
@Override
public void customize(String key, String value) {
customizations.put(key, value);
}

// Get the final email content
@Override
public String getContent() {
String personalizedBody = body.replace("[Name]", customizations.getOrDefault("name", "Customer"));
String offer = customizations.getOrDefault("offer", "");
return "Subject: " + subject + "\nBody: " + personalizedBody + "\nOffer: " + offer + "\nImage Size: " + bannerImage.length + " bytes";
}

// Clone method for Prototype
@Override
public WelcomeEmailTemplate clone() {
try {
WelcomeEmailTemplate clone = (WelcomeEmailTemplate) super.clone();
// Deep copy mutable fields
clone.customizations = new HashMap<>(this.customizations);
return clone;
} catch (CloneNotSupportedException e) {
throw new RuntimeException("Clone failed");
}
}
}

// Usage
public class Main {
public static void main(String[] args) {
// Create template ONCE (expensive: DB + file)
WelcomeEmailTemplate template = new WelcomeEmailTemplate();

// Clone for Recipient 1
EmailTemplate email1 = template.clone();
email1.customize("name", "Alice");
email1.customize("offer", "10% off your first purchase!");

// Clone for Recipient 2
EmailTemplate email2 = template.clone();
email2.customize("name", "Bob");
email2.customize("offer", "Free shipping!");

// Print emails
System.out.println("Email 1:\n" + email1.getContent());
System.out.println("Email 2:\n" + email2.getContent());
}
}

Trigger Word: “Clone it”

Analogy: You’ve got a template resume in Word. For each job application, you make a copy and tweak it (change skills, experience). You don’t reuse the same resume or start from scratch every time. Prototype creates new objects by copying a template.

Why Use It?: Creates new objects by copying a template, avoiding costly initialization and preventing shared state issues.

Hope you have got understanding of creational design patterns . This is my understanding of creational design patterns which I tried to explain in simple way . Let me know if you have any feedbacks for me. Hope you got to learn something. Keep learning, keep questioning, stay curious!

--

--

Jyoti
Jyoti

Written by Jyoti

Explorer, Observer, Curious and a head full of questions and thoughts.

No responses yet