FroquizFroquiz
HomeQuizzesSenior ChallengeGet CertifiedBlogAbout
Sign InStart Quiz
Sign InStart Quiz
Froquiz

The most comprehensive quiz platform for software engineers. Test yourself with 10000+ questions and advance your career.

LinkedIn

Platform

  • Start Quizzes
  • Topics
  • Blog
  • My Profile
  • Sign In

About

  • About Us
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

Β© 2026 Froquiz. All rights reserved.Built with passion for technology
Blog & Articles

Java Design Patterns: Singleton, Factory, Builder, Observer, Strategy and More

Master essential Java design patterns with practical examples. Covers creational patterns (Singleton, Factory, Builder), structural patterns (Decorator, Proxy, Adapter), and behavioral patterns (Observer, Strategy, Command).

Yusuf SeyitoğluMarch 18, 20261 views11 min read

Java Design Patterns: Singleton, Factory, Builder, Observer, Strategy and More

Design patterns are reusable solutions to commonly recurring software design problems. In Java interviews, you are expected to know them by name, explain their intent, identify trade-offs, and implement them. This guide covers the most frequently tested patterns with real Java examples.

Creational Patterns

Singleton

Ensures only one instance of a class exists across the application:

java
-- Thread-safe Singleton using double-checked locking public class DatabaseConnectionPool { private static volatile DatabaseConnectionPool instance; private final List<Connection> pool = new ArrayList<>(); private DatabaseConnectionPool() { -- Initialize connection pool for (int i = 0; i < 10; i++) { pool.add(createConnection()); } } public static DatabaseConnectionPool getInstance() { if (instance == null) { synchronized (DatabaseConnectionPool.class) { if (instance == null) { instance = new DatabaseConnectionPool(); } } } return instance; } public Connection getConnection() { ... } } -- Better: enum Singleton (thread-safe, serialization-safe) public enum AppConfig { INSTANCE; private final Properties props = loadProperties(); public String get(String key) { return props.getProperty(key); } } -- Usage AppConfig.INSTANCE.get("database.url");

Factory Method

Define an interface for creating objects, but let subclasses decide which class to instantiate:

java
-- Abstract product public interface Notification { void send(String recipient, String message); } -- Concrete products public class EmailNotification implements Notification { public void send(String recipient, String message) { System.out.printf("Email to %s: %s%n", recipient, message); } } public class SmsNotification implements Notification { public void send(String recipient, String message) { System.out.printf("SMS to %s: %s%n", recipient, message); } } public class PushNotification implements Notification { public void send(String recipient, String message) { System.out.printf("Push to %s: %s%n", recipient, message); } } -- Factory public class NotificationFactory { public static Notification create(String type) { return switch (type.toLowerCase()) { case "email" -> new EmailNotification(); case "sms" -> new SmsNotification(); case "push" -> new PushNotification(); default -> throw new IllegalArgumentException("Unknown notification type: " + type); }; } } -- Usage Notification n = NotificationFactory.create("email"); n.send("alice@example.com", "Your order has shipped!");

Builder

Construct complex objects step by step, especially when many optional parameters are involved:

java
public class HttpRequest { private final String url; private final String method; private final Map<String, String> headers; private final String body; private final int timeoutMs; private final int retries; private HttpRequest(Builder builder) { this.url = builder.url; this.method = builder.method; this.headers = Collections.unmodifiableMap(builder.headers); this.body = builder.body; this.timeoutMs = builder.timeoutMs; this.retries = builder.retries; } public static class Builder { private final String url; -- required private String method = "GET"; -- optional with default private Map<String, String> headers = new HashMap<>(); private String body; private int timeoutMs = 5000; private int retries = 0; public Builder(String url) { this.url = Objects.requireNonNull(url, "URL cannot be null"); } public Builder method(String method) { this.method = method; return this; } public Builder header(String name, String value) { this.headers.put(name, value); return this; } public Builder body(String body) { this.body = body; return this; } public Builder timeout(int ms) { this.timeoutMs = ms; return this; } public Builder retries(int retries) { this.retries = retries; return this; } public HttpRequest build() { if ("POST".equals(method) && body == null) { throw new IllegalStateException("POST request requires a body"); } return new HttpRequest(this); } } } -- Usage HttpRequest request = new HttpRequest.Builder("https://api.example.com/users") .method("POST") .header("Content-Type", "application/json") .header("Authorization", "Bearer token123") .body("{\"name\": \"Alice\"}") .timeout(10_000) .retries(3) .build();

Structural Patterns

Decorator

Add behavior to objects dynamically without modifying their class:

java
public interface DataSource { void writeData(String data); String readData(); } public class FileDataSource implements DataSource { private final String filename; public FileDataSource(String filename) { this.filename = filename; } public void writeData(String data) { -- Write to file } public String readData() { -- Read from file return ""; } } -- Base decorator public abstract class DataSourceDecorator implements DataSource { protected final DataSource wrapped; public DataSourceDecorator(DataSource source) { this.wrapped = source; } } -- Concrete decorators public class EncryptionDecorator extends DataSourceDecorator { public EncryptionDecorator(DataSource source) { super(source); } public void writeData(String data) { wrapped.writeData(encrypt(data)); } public String readData() { return decrypt(wrapped.readData()); } private String encrypt(String data) { return Base64.getEncoder().encodeToString(data.getBytes()); } private String decrypt(String data) { return new String(Base64.getDecoder().decode(data)); } } public class CompressionDecorator extends DataSourceDecorator { public CompressionDecorator(DataSource source) { super(source); } public void writeData(String data) { wrapped.writeData(compress(data)); } public String readData() { return decompress(wrapped.readData()); } private String compress(String data) { return data; } -- simplified private String decompress(String data) { return data; } -- simplified } -- Usage: stack decorators DataSource source = new FileDataSource("data.txt"); source = new CompressionDecorator(source); source = new EncryptionDecorator(source); source.writeData("Hello World"); -- encrypts(compresses("Hello World")) source.readData(); -- decompresses(decrypts(file contents))

Proxy

Provide a substitute that controls access to another object:

java
public interface UserService { User getUser(Long id); void updateUser(User user); } -- Real implementation public class UserServiceImpl implements UserService { public User getUser(Long id) { return db.findById(id); } public void updateUser(User user) { db.save(user); } } -- Caching proxy public class CachingUserServiceProxy implements UserService { private final UserService delegate; private final Map<Long, User> cache = new ConcurrentHashMap<>(); public CachingUserServiceProxy(UserService delegate) { this.delegate = delegate; } public User getUser(Long id) { return cache.computeIfAbsent(id, delegate::getUser); } public void updateUser(User user) { delegate.updateUser(user); cache.put(user.getId(), user); -- update cache } } -- Logging proxy public class LoggingUserServiceProxy implements UserService { private final UserService delegate; private final Logger log = LoggerFactory.getLogger(getClass()); public User getUser(Long id) { log.info("Fetching user {}", id); long start = System.currentTimeMillis(); User user = delegate.getUser(id); log.info("Fetched user {} in {}ms", id, System.currentTimeMillis() - start); return user; } public void updateUser(User user) { log.info("Updating user {}", user.getId()); delegate.updateUser(user); } }

Behavioral Patterns

Observer (Event Listener)

Define a one-to-many dependency so when one object changes state, all dependents are notified:

java
public interface EventListener<T> { void onEvent(T event); } public class EventBus { private final Map<Class<?>, List<EventListener<?>>> listeners = new ConcurrentHashMap<>(); @SuppressWarnings("unchecked") public <T> void subscribe(Class<T> eventType, EventListener<T> listener) { listeners.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>()) .add(listener); } @SuppressWarnings("unchecked") public <T> void publish(T event) { List<EventListener<?>> eventListeners = listeners.get(event.getClass()); if (eventListeners != null) { for (EventListener listener : eventListeners) { listener.onEvent(event); } } } } -- Events public record OrderPlacedEvent(Long orderId, Long userId, BigDecimal total) {} public record OrderShippedEvent(Long orderId, String trackingNumber) {} -- Listeners public class EmailNotificationService { public void onOrderPlaced(OrderPlacedEvent event) { sendEmail(event.userId(), "Your order #" + event.orderId() + " has been placed!"); } } -- Usage EventBus eventBus = new EventBus(); EmailNotificationService emailService = new EmailNotificationService(); eventBus.subscribe(OrderPlacedEvent.class, emailService::onOrderPlaced); eventBus.subscribe(OrderPlacedEvent.class, event -> analyticsService.track(event)); eventBus.publish(new OrderPlacedEvent(42L, 7L, new BigDecimal("99.99")));

Strategy

Define a family of algorithms, encapsulate each, and make them interchangeable:

java
public interface SortStrategy { void sort(int[] array); } public class BubbleSortStrategy implements SortStrategy { public void sort(int[] array) { for (int i = 0; i < array.length - 1; i++) { for (int j = 0; j < array.length - i - 1; j++) { if (array[j] > array[j + 1]) { int temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; } } } } } public class QuickSortStrategy implements SortStrategy { public void sort(int[] array) { quickSort(array, 0, array.length - 1); } -- quickSort implementation... } public class Sorter { private SortStrategy strategy; public Sorter(SortStrategy strategy) { this.strategy = strategy; } public void setStrategy(SortStrategy strategy) { this.strategy = strategy; } public void sort(int[] array) { strategy.sort(array); } } -- Java 8+ functional style public class PaymentProcessor { private final Map<String, Consumer<Payment>> handlers = new HashMap<>(); public PaymentProcessor() { handlers.put("credit_card", this::processCreditCard); handlers.put("paypal", this::processPayPal); handlers.put("crypto", this::processCrypto); } public void process(Payment payment) { Consumer<Payment> handler = handlers.get(payment.getMethod()); if (handler == null) throw new IllegalArgumentException("Unknown method: " + payment.getMethod()); handler.accept(payment); } private void processCreditCard(Payment p) { System.out.println("Processing credit card: " + p); } private void processPayPal(Payment p) { System.out.println("Processing PayPal: " + p); } private void processCrypto(Payment p) { System.out.println("Processing crypto: " + p); } }

Command

Encapsulate requests as objects, enabling undo, logging, and queuing:

java
public interface Command { void execute(); void undo(); } public class TextEditor { private StringBuilder content = new StringBuilder(); private final Deque<Command> history = new ArrayDeque<>(); public void executeCommand(Command command) { command.execute(); history.push(command); } public void undo() { if (!history.isEmpty()) { history.pop().undo(); } } -- Inner command classes public class InsertCommand implements Command { private final int position; private final String text; public InsertCommand(int position, String text) { this.position = position; this.text = text; } public void execute() { content.insert(position, text); } public void undo() { content.delete(position, position + text.length()); } } public class DeleteCommand implements Command { private final int start, end; private String deletedText; public DeleteCommand(int start, int end) { this.start = start; this.end = end; } public void execute() { deletedText = content.substring(start, end); content.delete(start, end); } public void undo() { content.insert(start, deletedText); } } } -- Usage TextEditor editor = new TextEditor(); editor.executeCommand(editor.new InsertCommand(0, "Hello")); editor.executeCommand(editor.new InsertCommand(5, " World")); editor.undo(); -- removes " World" editor.undo(); -- removes "Hello"

Common Interview Questions

Q: What is the difference between the Factory Method and Abstract Factory patterns?

Factory Method defines one method for creating one type of object β€” subclasses decide the concrete type. Abstract Factory provides an interface for creating families of related objects β€” a concrete factory creates multiple related products. Use Factory Method for one product type; use Abstract Factory when you need multiple related products that must be used together (e.g., UI components for a theme: Button, TextField, Checkbox all from the same factory).

Q: When would you use the Proxy pattern vs the Decorator pattern?

Both wrap an object. Decorator adds behavior to enhance functionality β€” the client typically knows they are using a decorator. Proxy controls access β€” the client typically does not know a proxy is involved (it appears to be the real object). Proxies are used for lazy initialization, access control, caching, logging, and remote objects.

Q: What is the difference between the Strategy and State patterns?

Both encapsulate behavior. Strategy encapsulates an algorithm that can be changed externally by the client β€” the object uses different algorithms. State encapsulates behavior that changes based on the object's internal state β€” the object itself transitions between states, changing its behavior. In Strategy, the client selects the strategy; in State, the object itself manages state transitions.

Practice Java on Froquiz

Design patterns are tested in mid-level and senior Java interviews. Test your Java knowledge on Froquiz β€” covering OOP, collections, concurrency, and patterns.

Summary

  • Singleton: one instance β€” use enum for thread safety and serialization safety
  • Factory Method: create objects by type without exposing concrete classes
  • Builder: construct complex objects with many optional parameters using fluent API
  • Decorator: add behavior dynamically by wrapping objects β€” stackable and composable
  • Proxy: control access to an object β€” caching, logging, security, lazy initialization
  • Observer: one-to-many notification β€” publishers do not know their subscribers
  • Strategy: swap algorithms at runtime β€” favors composition over inheritance
  • Command: encapsulate requests as objects β€” enables undo, queuing, and logging

About Author

Yusuf Seyitoğlu

Author β†’

Other Posts

  • Python Concurrency: Threading, Multiprocessing and the GIL ExplainedMar 18
  • CSS Architecture: BEM, SMACSS, Utility-First and Modern ApproachesMar 18
  • SQL Joins and Aggregations Deep Dive: INNER, LEFT, CROSS, SELF, GROUP BY and HAVINGMar 18
All Blogs