A design pattern is a named solution to a design problem that keeps coming up. Interviewers use patterns as shorthand. When they ask about one, they are checking whether you know the problem it solves, not whether you can redraw a class diagram.
This post covers the eight patterns that come up most, in Java. Each one follows the same steps: the problem, shown without the pattern; the idea; the code; where you have already used it in the JDK; how it differs from a look-alike; and what it costs.
It is part 3 of the Interview Essentials series. The principles behind patterns, especially Open/Closed and Dependency Inversion, are in the Engineering Fundamentals Refresh.
How to answer a pattern question
- Name the problem first, in one sentence.
- Name the pattern that solves it.
- Sketch the classes: usually an interface and two or three implementations.
- Give one real example and one trade-off.
Creational patterns
1. Singleton
The problem. Some things should exist once: configuration, a connection pool, a registry. If every caller writes new Config(), you get several copies that can disagree, and each one loads the file again.
The idea. Make the constructor private and hand out one instance through a static method.
The follow-up that always comes: thread safety. The obvious lazy version is wrong when two threads call it at once. Step through it:
Try it: two threads and a lazy singleton
The most common follow-up question: why is lazy initialisation unsafe? Step through the same two threads with and without synchronization.
public static Config getInstance() { if (instance == null) { instance = new Config(); } return instance;}
instance
null
lock held by
nobody
objects created
0
Threads A and B call getInstance() at about the same time. instance is null.
Three ways to get it right:
// 1. Enum: simplest, and safe against reflection and serialization tricks
public enum Config {
INSTANCE;
public String get(String key) { /* ... */ return ""; }
}
// 2. Holder idiom: lazy and thread-safe, with no explicit locking
public final class Config {
private Config() {}
private static class Holder { static final Config INSTANCE = new Config(); }
public static Config getInstance() { return Holder.INSTANCE; }
}
// 3. Double-checked locking: the field must be volatile
public final class Config {
private static volatile Config instance;
private Config() {}
public static Config getInstance() {
if (instance == null) {
synchronized (Config.class) {
if (instance == null) instance = new Config();
}
}
return instance;
}
}The holder idiom works because the JVM initialises a class once, on first use, and does so thread-safely. The Holder class is not loaded until getInstance() is first called.
Where you have seen it. Runtime.getRuntime() returns the single runtime object of the JVM. Spring beans are singletons by default, but that means one instance per container, not a private constructor.
Compare. A static utility class also gives you one shared thing, but it cannot implement an interface and cannot be swapped for a fake in a test. A singleton can.
Trade-offs. It is global state in disguise: any code can reach it, so dependencies become hidden and tests interfere with each other. In most applications it is better to create one instance and pass it in, and let a dependency injection container make it single.
Check yourself
Which Singleton implementation is also safe against reflection and serialization attacks?
2. Factory
The problem. Code that decides which class to build ends up repeated everywhere:
Notification n;
if (type.equals("email")) n = new EmailNotification();
else if (type.equals("sms")) n = new SmsNotification();
else throw new IllegalArgumentException(type);Adding a push notification means finding and editing every copy of that block.
The idea. Move the decision into one place that returns the interface type.
interface Notification { void send(String message); }
class EmailNotification implements Notification { public void send(String m) { /* ... */ } }
class SmsNotification implements Notification { public void send(String m) { /* ... */ } }
class NotificationFactory {
static Notification create(String type) {
return switch (type) {
case "email" -> new EmailNotification();
case "sms" -> new SmsNotification();
default -> throw new IllegalArgumentException("Unknown type: " + type);
};
}
}
Notification n = NotificationFactory.create("sms");
n.send("Your code is 4821");Where you have seen it. Calendar.getInstance(), NumberFormat.getInstance(), List.of(...) and Integer.valueOf(...) are all static factory methods. You never name the concrete class they return.
Compare. The version above is usually called a simple factory or static factory. The Gang of Four's Factory Method is a little different: a base class declares a create() method and each subclass decides which object to return. In an interview, say which one you mean. A factory decides which object you get. A builder, next, controls how one complex object is put together.
Trade-offs. One place to change when types are added, and callers depend only on the interface. The cost is an extra class, and a factory with a growing switch can itself need refactoring once there are many types.
3. Builder
The problem. A class with many fields, most of them optional, leads to this:
new User("Ana", 30, null, null, true, false); // what are the last four?The usual workaround, one constructor per combination, does not scale.
The idea. Collect the values one named step at a time, then create the object once.
public final class User {
private final String name;
private final int age;
private final String email;
private User(Builder b) { name = b.name; age = b.age; email = b.email; }
public static class Builder {
private String name;
private int age;
private String email;
public Builder name(String name) { this.name = name; return this; }
public Builder age(int age) { this.age = age; return this; }
public Builder email(String email) { this.email = email; return this; }
public User build() {
if (name == null) throw new IllegalStateException("name is required");
return new User(this);
}
}
}
User u = new User.Builder().name("Ana").age(30).build();Each setter returns this, which is what makes the chain possible. build() is the single place to validate.
Where you have seen it. StringBuilder.append(...), HttpRequest.newBuilder() (Java 11) and Stream.builder(). Lombok's @Builder generates this code for you, but Lombok is a library, not part of the JDK.
Compare. For a small immutable data class, a Java record (Java 16 and later) gives you a constructor and getters in one line and often removes the need for a builder. Reach for a builder when there are many optional fields.
Trade-offs. Readable construction and an immutable result, but roughly twice the code of a plain constructor.
Structural patterns
4. Adapter
The problem. You need to use a class you cannot change, such as a third-party library or legacy code, but its methods do not match the interface your code expects.
The idea. Write a small class that implements your interface and translates each call into the call the old class understands.
interface PaymentGateway { void pay(double amount); }
class LegacyBankApi { // cannot be changed
void makeTransfer(long cents, String account) { /* ... */ }
}
class BankAdapter implements PaymentGateway {
private final LegacyBankApi bank;
private final String account;
BankAdapter(LegacyBankApi bank, String account) { this.bank = bank; this.account = account; }
public void pay(double amount) {
bank.makeTransfer(Math.round(amount * 100), account); // translate
}
}Where you have seen it. InputStreamReader adapts a byte stream (InputStream) to a character stream (Reader). Arrays.asList(array) presents an array through the List interface.
Trade-offs. Neither side changes, and you can swap the legacy API later by writing another adapter. The cost is one more class and a translation layer to keep correct.
5. Decorator
The problem. You want optional features in any combination. With subclasses, three add-ons already mean eight combinations, each its own class (EspressoWithMilk, EspressoWithMilkAndSugar and so on), and every new add-on doubles the count.
The idea. Wrap the object in another object that has the same interface, adds its own part, and passes the call inward. Wrappers can stack.
interface Coffee { double cost(); String description(); }
class Espresso implements Coffee {
public double cost() { return 2.00; }
public String description() { return "Espresso"; }
}
abstract class CoffeeDecorator implements Coffee {
protected final Coffee inner;
CoffeeDecorator(Coffee inner) { this.inner = inner; }
}
class Milk extends CoffeeDecorator {
Milk(Coffee inner) { super(inner); }
public double cost() { return inner.cost() + 0.50; }
public String description() { return inner.description() + ", milk"; }
}
Coffee order = new Sugar(new Milk(new Espresso())); // Sugar works the same way as MilkTry it: stack decorators
Each add-on wraps the order so far. No new subclass is needed for any combination.
Coffee order = new Sugar(new Milk(new Espresso()));
description()
Espresso, milk, sugar
cost()
2.70
- Sugar.cost() = 0.20 + inner.cost()
- Milk.cost() = 0.50 + inner.cost()
- Espresso.cost() = 2.00
Where you have seen it. Java I/O is built this way:
new BufferedReader(new InputStreamReader(new FileInputStream("data.txt")));FileInputStream reads bytes. InputStreamReader adapts bytes to characters. BufferedReader decorates that with buffering and readLine(). Collections.unmodifiableList(list) also wraps a list in the same interface while blocking changes.
Compare. Inheritance fixes the combination at compile time. Decorators are chosen and stacked at run time.
Trade-offs. Flexible combinations without a class explosion. The costs: many small classes, the order of wrapping can matter, and you cannot easily remove a layer from the middle.
6. Facade
The problem. Placing an order means calling inventory, payment and shipping in the right order, and every caller repeats it.
The idea. Put one simple class in front of the complicated set of classes.
class OrderFacade {
private final Inventory inventory;
private final Payment payment;
private final Shipping shipping;
OrderFacade(Inventory i, Payment p, Shipping s) { inventory = i; payment = p; shipping = s; }
OrderResult placeOrder(Order order) {
inventory.reserve(order.items());
payment.charge(order.customer(), order.total()); // real code releases the reservation if this fails
shipping.schedule(order);
return OrderResult.confirmed(order.id());
}
}Where you have seen it. SLF4J is literally the "Simple Logging Facade for Java". Your code calls LoggerFactory.getLogger(...), and one of several logging frameworks does the work behind it.
Trade-offs. Callers get a small, stable API. Watch for two problems: a facade that grows into a class that does everything, and a facade that hides subsystems some callers still need to reach directly.
Adapter, Decorator and Facade side by side
These three look alike on a class diagram and are the most confused trio.
| Adapter | Decorator | Facade | |
|---|---|---|---|
| Goal | Make an incompatible interface fit | Add behaviour | Simplify a set of classes |
| Interface | Converts one interface to another | Same as the wrapped object | A new, simpler one |
| Wraps | One object | One object, often stacked | Many objects |
Check yourself
You need to add caching to an existing data source without changing its interface or its class. Which pattern fits?
Behavioural patterns
7. Strategy
The problem. Rules that vary pile up in an if/else, and every new rule means editing code that already works:
double total(double subtotal, String customerType) {
if (customerType.equals("festive")) return subtotal * 0.80;
if (customerType.equals("bulk")) return subtotal * 0.90;
return subtotal;
}The idea. Put each rule behind one interface and let the caller hand in the one it wants.
interface DiscountStrategy { double apply(double price); }
class Checkout {
private final DiscountStrategy strategy;
Checkout(DiscountStrategy strategy) { this.strategy = strategy; }
double total(double subtotal) { return strategy.apply(subtotal); }
}
// a one-method strategy needs no class when you use a lambda
Checkout festive = new Checkout(price -> price * 0.80);Try it: swap the strategy
Checkout calls strategy.apply(subtotal) and nothing else. Each item costs 20.00. Change the rule or the item count and watch the total change while the Checkout code stays the same.
class NoDiscount implements DiscountStrategy {
double apply(double price) {
return price;
}
}subtotal
60.00
total after strategy
60.00
Where you have seen it. The Comparator you pass to list.sort(...) is a strategy: the sorting algorithm stays fixed and you swap the comparison rule. The rejection policies of ThreadPoolExecutor (AbortPolicy, CallerRunsPolicy and others) are strategies too.
Compare. The State pattern looks the same in code, but the object switches its own behaviour as it moves between states. With Strategy, the caller chooses.
Trade-offs. New rules arrive as new classes, and each can be tested alone. The cost is more classes, and callers must know which strategy to pick. For two simple cases, a plain if is fine.
Check yourself
A method picks a shipping cost with a long if/else on the carrier name, and a new carrier is added every quarter. Which pattern removes the if/else?
8. Observer
The problem. One object changes and several others need to react. If the changing object calls each of them directly, it must know about all of them, and every new listener means editing it.
The idea. The changing object (the subject) keeps a list of subscribers behind a small interface and calls each one when something happens. Subscribers can join and leave at run time.
interface Subscriber { void update(String message); }
class Newsletter {
private final List<Subscriber> subscribers = new ArrayList<>();
void subscribe(Subscriber s) { subscribers.add(s); }
void unsubscribe(Subscriber s) { subscribers.remove(s); }
void publish(String message) { for (Subscriber s : subscribers) s.update(message); }
}
newsletter.subscribe(msg -> System.out.println("Email: " + msg));Try it: subscribe and publish
The newsletter does not know what its subscribers do. It only calls update on each one that is subscribed.
- Nothing published yet.
Where you have seen it. addEventListener in the browser, ActionListener in Swing and PropertyChangeListener in java.beans. Java's own java.util.Observable and Observer classes have been deprecated since Java 9, so use a listener interface or the java.util.concurrent.Flow interfaces instead.
Compare. With Observer, many listeners are told something happened. With Strategy, one behaviour is chosen.
Trade-offs. The subject and its listeners are loosely coupled. The costs: update order is not obvious, one update can trigger a chain of others that is hard to trace, and a listener that is never unsubscribed stays in memory.
Check yourself
A long-lived object keeps a list of listeners, and some listeners are never removed. What is the most likely problem?
Also worth knowing
| Pattern | One line | Where you have seen it |
|---|---|---|
| Proxy | A stand-in that controls access to another object | java.lang.reflect.Proxy; Spring uses proxies to add transactions |
| Command | Wrap a request as an object so it can be queued or undone | A Runnable submitted to an ExecutorService |
| Template Method | A fixed skeleton with steps that subclasses fill in | HttpServlet.service() calls doGet or doPost |
| Iterator | Walk a collection without exposing how it is stored | java.util.Iterator and the enhanced for loop |
| State | Behaviour changes with the object's internal state | An order moving through placed, paid, shipped |
Which pattern for which problem
| If the problem is... | Reach for |
|---|---|
| Exactly one shared instance is needed | Singleton, or let a DI container make it single |
| Callers should not name concrete classes | Factory |
| Many optional constructor arguments | Builder |
| An existing class has the wrong interface | Adapter |
| Add features in any combination at run time | Decorator |
| Callers face a tangle of classes | Facade |
| Swap an algorithm or a business rule | Strategy |
| Many objects must react to one change | Observer |
Common mistakes
- Naming a pattern without saying which problem it solves. Start with the problem.
- Adding a pattern to code that does not need one. A
switchwith two stable cases does not need Strategy. - Using Singleton for something that should simply be passed in.
- Mixing up look-alikes: Adapter, Decorator and Facade; Strategy and State.
Sources and further reading
- Gamma, Helm, Johnson and Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (1994): the original catalogue.
- Joshua Bloch, Effective Java, third edition (2018): Item 1 on static factory methods, Item 2 on builders and Item 3 on singletons.
- Freeman and Robson, Head First Design Patterns, second edition (2020): a gentler, example-led introduction.
- The SLF4J user manual, for the facade example.
- The Java SE API documentation for
java.io,java.util.Comparatorandjava.net.http.HttpRequest.
This is part 3 of the Interview Essentials series. Next is the JavaScript guide.
