Practice Questions — Introduction to Design Patterns
← Back to NotesTopic-Specific Questions
Question 1
Easy
What is the output?
public enum Config {
INSTANCE;
private int value = 0;
public void set(int v) { value = v; }
public int get() { return value; }
}
Config c1 = Config.INSTANCE;
Config c2 = Config.INSTANCE;
c1.set(42);
System.out.println(c2.get());Enum constants are singletons. c1 and c2 are the same object.
42Question 2
Easy
What is the output?
class Singleton {
private static Singleton instance = new Singleton();
private int count = 0;
private Singleton() { count++; }
public static Singleton getInstance() { return instance; }
public int getCount() { return count; }
}
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
System.out.println(s1.getCount());The constructor runs only once (eager initialization).
true1Question 3
Easy
What is the output?
interface Animal { String speak(); }
class Dog implements Animal { public String speak() { return "Woof"; } }
class Cat implements Animal { public String speak() { return "Meow"; } }
class AnimalFactory {
static Animal create(String type) {
if (type.equals("dog")) return new Dog();
if (type.equals("cat")) return new Cat();
throw new IllegalArgumentException("Unknown");
}
}
Animal a = AnimalFactory.create("cat");
System.out.println(a.speak());The factory creates a Cat when given "cat".
MeowQuestion 4
Easy
What is the output?
interface Greeting { void greet(String name); }
class Formal implements Greeting {
public void greet(String name) { System.out.println("Good day, " + name); }
}
class Casual implements Greeting {
public void greet(String name) { System.out.println("Hey, " + name + "!"); }
}
Greeting g = new Casual();
g.greet("Arjun");
g = new Formal();
g.greet("Arjun");The strategy (greeting style) changes at runtime.
Hey, Arjun!Good day, ArjunQuestion 5
Medium
What is the output?
class Builder {
private String name = "";
private int value = 0;
Builder name(String n) { name = n; return this; }
Builder value(int v) { value = v; return this; }
String build() { return name + ":" + value; }
}
String result = new Builder().name("test").value(42).build();
System.out.println(result);Each method returns 'this' for chaining.
test:42Question 6
Medium
What is the output?
interface Observer { void update(String msg); }
class Subject {
List<Observer> observers = new ArrayList<>();
void add(Observer o) { observers.add(o); }
void notifyAll(String msg) {
for (Observer o : observers) o.update(msg);
}
}
Subject s = new Subject();
s.add(msg -> System.out.println("A: " + msg));
s.add(msg -> System.out.println("B: " + msg));
s.notifyAll("Hello");Two observers are registered. Both receive the notification.
A: HelloB: HelloQuestion 7
Medium
What happens when this code runs?
class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) instance = new Singleton();
return instance;
}
}
// Called from two threads simultaneously:
// Thread 1: Singleton.getInstance()
// Thread 2: Singleton.getInstance()This lazy initialization is not thread-safe.
Both threads could see
instance == null simultaneously and create two separate instances. This violates the Singleton contract. The first thread creates one instance, the second creates another.Question 8
Medium
What is the difference between the Factory pattern and the Builder pattern?
Think about what problem each solves.
The Factory pattern selects which class to instantiate based on input (choosing between Circle, Rectangle, Triangle). The Builder pattern constructs a single complex object step by step with many optional parameters (setting name, email, phone, address on a Student). Factory answers 'which type?' while Builder answers 'how to configure it?'.
Question 9
Hard
Why is the enum Singleton considered the best implementation? What problems does it solve that other approaches do not?
Think about reflection, serialization, and thread safety.
Enum Singleton solves three problems: (1) Thread safety: the JVM guarantees that enum instances are created exactly once in a thread-safe manner. (2) Serialization safety: enum deserialization always returns the same instance (Java handles this specially). Other Singletons may create a new instance during deserialization. (3) Reflection safety: you cannot create new enum instances via reflection (the JVM prevents it). Other Singletons can be broken by
Constructor.setAccessible(true).Question 10
Hard
When should you NOT use the Singleton pattern?
Think about testability and coupling.
Avoid Singleton when: (1) You need testability -- Singletons are hard to mock/stub in unit tests because they carry global state. (2) You need different configurations -- Singleton provides one fixed instance, which cannot be configured differently per context. (3) The class does not truly need to be unique. Prefer dependency injection -- let a DI container manage singleton scope instead of hardcoding it into the class. This allows easy substitution for testing and different environments.
Question 11
Hard
How does the Observer pattern support the Open/Closed Principle?
Think about adding new observers without modifying the subject.
The Open/Closed Principle states that classes should be open for extension but closed for modification. The Observer pattern supports this because you can add new observer types (new classes implementing the Observer interface) without modifying the Subject class. The Subject only knows the Observer interface, not concrete types. Adding an SMS notifier, Slack notifier, or any other observer requires zero changes to the Subject.
Mixed & Application Questions
Question 1
Easy
What is the output?
class Logger {
private static final Logger INSTANCE = new Logger();
private int logCount = 0;
private Logger() {}
static Logger getInstance() { return INSTANCE; }
void log(String msg) {
logCount++;
System.out.println(logCount + ": " + msg);
}
}
Logger.getInstance().log("Start");
Logger.getInstance().log("Process");
Logger.getInstance().log("End");All three calls use the same Singleton instance.
1: Start2: Process3: EndQuestion 2
Easy
What is the output?
interface Formatter { String format(String text); }
class Upper implements Formatter { public String format(String t) { return t.toUpperCase(); } }
class Lower implements Formatter { public String format(String t) { return t.toLowerCase(); } }
Formatter f = new Upper();
System.out.println(f.format("Hello"));
f = new Lower();
System.out.println(f.format("Hello"));The strategy (formatter) changes at runtime.
HELLOhelloQuestion 3
Medium
What is the output?
class Pizza {
String size, crust;
boolean cheese, pepperoni;
public String toString() { return size + "/" + crust
+ (cheese ? "+cheese" : "") + (pepperoni ? "+pepperoni" : ""); }
static class Builder {
String size, crust = "thin";
boolean cheese, pepperoni;
Builder(String size) { this.size = size; }
Builder crust(String c) { crust = c; return this; }
Builder cheese() { cheese = true; return this; }
Builder pepperoni() { pepperoni = true; return this; }
Pizza build() {
Pizza p = new Pizza(); p.size = size; p.crust = crust;
p.cheese = cheese; p.pepperoni = pepperoni; return p;
}
}
}
Pizza p = new Pizza.Builder("Large").cheese().pepperoni().build();
System.out.println(p);Default crust is "thin". cheese() and pepperoni() are called.
Large/thin+cheese+pepperoniQuestion 4
Medium
What is the output?
interface Calculator { int compute(int a, int b); }
class Add implements Calculator { public int compute(int a, int b) { return a + b; } }
class Multiply implements Calculator { public int compute(int a, int b) { return a * b; } }
class Engine {
Calculator strategy;
void setStrategy(Calculator c) { strategy = c; }
int execute(int a, int b) { return strategy.compute(a, b); }
}
Engine e = new Engine();
e.setStrategy(new Add());
System.out.println(e.execute(3, 4));
e.setStrategy(new Multiply());
System.out.println(e.execute(3, 4));The strategy changes from Add to Multiply.
712Question 5
Hard
What is the output?
interface Observer { void notify(int val); }
class Subject {
List<Observer> obs = new ArrayList<>();
int value;
void add(Observer o) { obs.add(o); }
void remove(Observer o) { obs.remove(o); }
void setValue(int v) {
value = v;
obs.forEach(o -> o.notify(v));
}
}
Subject s = new Subject();
Observer o1 = v -> System.out.println("O1: " + v);
Observer o2 = v -> System.out.println("O2: " + v);
s.add(o1);
s.add(o2);
s.setValue(10);
s.remove(o1);
s.setValue(20);o1 is removed before the second setValue call.
O1: 10O2: 10O2: 20Question 6
Easy
What are the three categories of design patterns defined by the Gang of Four?
Think about creating, composing, and communicating.
(1) Creational: patterns for object creation (Singleton, Factory, Builder, Prototype, Abstract Factory). (2) Structural: patterns for composing objects into larger structures (Adapter, Decorator, Proxy, Facade, Composite). (3) Behavioral: patterns for object communication and responsibility distribution (Observer, Strategy, Command, Iterator, State).
Question 7
Medium
What real-world Java framework uses the Observer pattern, and how?
Think about event handling in Java GUI or Spring.
Multiple frameworks use Observer: (1) Java Swing/AWT: ActionListener, MouseListener are observers registered on GUI components (subjects). (2) Spring Framework: ApplicationListener observes ApplicationEvents published by the event system. (3) Java Beans: PropertyChangeListener observes property changes. The common theme is decoupling event producers from event consumers.
Question 8
Hard
Compare the Singleton pattern with dependency injection. When would you prefer one over the other?
Think about testability and coupling.
Singleton pattern: hardcodes single-instance behavior into the class. Simple but creates tight coupling (code directly calls
getInstance()). Difficult to mock in tests. Dependency injection: a DI container (like Spring) manages the lifecycle. The class receives its dependencies through the constructor. Singleton scope is a container configuration, not a class design choice. Prefer DI when: testability matters, different environments need different configurations, or you use a framework (Spring, Guice). Prefer Singleton pattern when: no DI framework is available and you genuinely need a single global instance.Multiple Choice Questions
MCQ 1
What does the Singleton pattern guarantee?
Answer: B
B is correct. Singleton ensures exactly one instance of the class exists and provides a global access point (usually through
B is correct. Singleton ensures exactly one instance of the class exists and provides a global access point (usually through
getInstance()).MCQ 2
What is the key element that prevents external instantiation in Singleton?
Answer: B
B is correct. A private constructor prevents code outside the class from using
B is correct. A private constructor prevents code outside the class from using
new to create instances. Only the class itself (through getInstance()) can create the instance.MCQ 3
Which pattern creates objects without exposing the creation logic to the client?
Answer: C
C is correct. The Factory pattern hides object creation behind a factory method. The client requests an object by type and receives it without knowing which concrete class was instantiated.
C is correct. The Factory pattern hides object creation behind a factory method. The client requests an object by type and receives it without knowing which concrete class was instantiated.
MCQ 4
What problem does the Builder pattern solve?
Answer: B
B is correct. Builder solves the problem of constructing objects with many optional parameters. It avoids telescoping constructors and produces readable, immutable objects.
B is correct. Builder solves the problem of constructing objects with many optional parameters. It avoids telescoping constructors and produces readable, immutable objects.
MCQ 5
The Gang of Four book organizes patterns into how many categories?
Answer: B
B is correct. The GoF organizes 23 patterns into three categories: Creational, Structural, and Behavioral.
B is correct. The GoF organizes 23 patterns into three categories: Creational, Structural, and Behavioral.
MCQ 6
Why is the enum Singleton recommended over other implementations?
Answer: B
B is correct. Enum Singleton is inherently thread-safe (JVM guarantee), serialization-safe (always returns the same instance), and reflection-proof (cannot create new enum instances via reflection).
B is correct. Enum Singleton is inherently thread-safe (JVM guarantee), serialization-safe (always returns the same instance), and reflection-proof (cannot create new enum instances via reflection).
MCQ 7
In the Observer pattern, what is the Subject?
Answer: B
B is correct. The Subject (or Observable) maintains a list of observers, and when its state changes, it notifies all of them. Observers subscribe to and unsubscribe from the Subject.
B is correct. The Subject (or Observable) maintains a list of observers, and when its state changes, it notifies all of them. Observers subscribe to and unsubscribe from the Subject.
MCQ 8
What technique does the Builder pattern use to make code readable?
Answer: B
B is correct. Each setter method in the Builder returns
B is correct. Each setter method in the Builder returns
this, enabling method chaining: builder.name("X").age(20).build(). This creates a fluent, readable API.MCQ 9
Which pattern allows selecting an algorithm at runtime?
Answer: D
D is correct. The Strategy pattern encapsulates algorithms into interchangeable classes. The client selects which algorithm to use at runtime by setting the strategy object.
D is correct. The Strategy pattern encapsulates algorithms into interchangeable classes. The client selects which algorithm to use at runtime by setting the strategy object.
MCQ 10
What is the volatile keyword used for in double-checked locking Singleton?
Answer: B
B is correct. Without
B is correct. Without
volatile, the JVM might reorder the assignment and constructor call, allowing a thread to see a non-null but incompletely initialized instance. volatile prevents this reordering.MCQ 11
Which is an anti-pattern to avoid?
Answer: C
C is correct. Making every service a Singleton creates tight coupling, makes testing difficult, and introduces global state. Use dependency injection instead.
C is correct. Making every service a Singleton creates tight coupling, makes testing difficult, and introduces global state. Use dependency injection instead.
MCQ 12
What is the difference between Factory Method and Abstract Factory?
Answer: B
B is correct. Factory Method creates one type of product. Abstract Factory creates families of related products (e.g., a UI factory that creates both buttons and checkboxes for a specific platform).
B is correct. Factory Method creates one type of product. Abstract Factory creates families of related products (e.g., a UI factory that creates both buttons and checkboxes for a specific platform).
MCQ 13
How can reflection break a Singleton (non-enum)?
Answer: B
B is correct.
B is correct.
Constructor.setAccessible(true) bypasses the private modifier, allowing creation of new instances. Enum Singletons are immune because the JVM prevents enum instantiation via reflection.MCQ 14
Which SOLID principle does the Strategy pattern primarily support?
Answer: B
B is correct. The Strategy pattern supports the Open/Closed Principle: new algorithms (strategies) can be added by creating new classes, without modifying existing client code or the context class.
B is correct. The Strategy pattern supports the Open/Closed Principle: new algorithms (strategies) can be added by creating new classes, without modifying existing client code or the context class.
MCQ 15
What is the return type of each setter method in a Builder class?
Answer: C
C is correct. Each setter returns
C is correct. Each setter returns
this (the Builder instance) to enable method chaining. Only the build() method returns the final product.MCQ 16
Which of these is a Behavioral design pattern?
Answer: C
C is correct. Observer is a Behavioral pattern (how objects communicate). Singleton, Builder, and Factory are Creational patterns (how objects are created).
C is correct. Observer is a Behavioral pattern (how objects communicate). Singleton, Builder, and Factory are Creational patterns (how objects are created).
Coding Challenges
Challenge 1: Enum Singleton Logger
EasyImplement a Logger as an enum Singleton with methods: info(msg), warn(msg), error(msg). Each method should print the log level, timestamp (simplified as a counter), and message. Verify that multiple references point to the same instance.
Sample Input
(No input required)
Sample Output
[1] INFO: Application started
[2] WARN: Low memory
[3] ERROR: NullPointerException
Same instance: true
Use enum Singleton. Include a message counter.
public enum Logger {
INSTANCE;
private int counter = 0;
public void info(String msg) { log("INFO", msg); }
public void warn(String msg) { log("WARN", msg); }
public void error(String msg) { log("ERROR", msg); }
private void log(String level, String msg) {
counter++;
System.out.printf("[%d] %s: %s%n", counter, level, msg);
}
public static void main(String[] args) {
Logger.INSTANCE.info("Application started");
Logger.INSTANCE.warn("Low memory");
Logger.INSTANCE.error("NullPointerException");
System.out.println("Same instance: " + (Logger.INSTANCE == Logger.INSTANCE));
}
}Challenge 2: Shape Factory with Area Calculation
MediumCreate a Shape interface with getArea() and getName(). Implement Circle, Rectangle, and Triangle. Build a ShapeFactory that creates shapes from string input. Calculate and print the area of each shape.
Sample Input
create("circle", 5), create("rectangle", 4, 6), create("triangle", 3, 8)
Sample Output
Circle: area = 78.54
Rectangle: area = 24.00
Triangle: area = 12.00
Use the Factory pattern. Return the Shape interface type, not concrete types.
interface Shape {
double getArea();
String getName();
}
class Circle implements Shape {
double radius;
Circle(double r) { radius = r; }
public double getArea() { return Math.PI * radius * radius; }
public String getName() { return "Circle"; }
}
class Rectangle implements Shape {
double width, height;
Rectangle(double w, double h) { width = w; height = h; }
public double getArea() { return width * height; }
public String getName() { return "Rectangle"; }
}
class Triangle implements Shape {
double base, height;
Triangle(double b, double h) { base = b; height = h; }
public double getArea() { return 0.5 * base * height; }
public String getName() { return "Triangle"; }
}
class ShapeFactory {
static Shape create(String type, double... dims) {
return switch (type.toLowerCase()) {
case "circle" -> new Circle(dims[0]);
case "rectangle" -> new Rectangle(dims[0], dims[1]);
case "triangle" -> new Triangle(dims[0], dims[1]);
default -> throw new IllegalArgumentException("Unknown: " + type);
};
}
}
public class FactoryChallenge {
public static void main(String[] args) {
Shape[] shapes = {
ShapeFactory.create("circle", 5),
ShapeFactory.create("rectangle", 4, 6),
ShapeFactory.create("triangle", 3, 8)
};
for (Shape s : shapes) {
System.out.printf("%s: area = %.2f%n", s.getName(), s.getArea());
}
}
}Challenge 3: Student Builder with Validation
MediumCreate a Student class using the Builder pattern. Required fields: name and age. Optional: email, phone, department, gpa. The build() method should validate: name must not be empty, age must be 16-30, gpa (if set) must be 0.0-10.0. Throw IllegalArgumentException for invalid data.
Sample Input
Builder("Arjun", 20).email("arjun@email.com").gpa(8.5).build()
Sample Output
Student{name=Arjun, age=20, email=arjun@email.com, gpa=8.5}
Use Builder pattern with method chaining. Validate in build(). Make Student immutable (final fields).
public class Student {
private final String name;
private final int age;
private final String email;
private final String phone;
private final String department;
private final double gpa;
private Student(Builder b) {
this.name = b.name; this.age = b.age;
this.email = b.email; this.phone = b.phone;
this.department = b.department; this.gpa = b.gpa;
}
public String toString() {
return String.format("Student{name=%s, age=%d, email=%s, gpa=%.1f}", name, age, email, gpa);
}
public static class Builder {
private final String name;
private final int age;
private String email = ""; private String phone = "";
private String department = ""; private double gpa = -1;
public Builder(String name, int age) { this.name = name; this.age = age; }
public Builder email(String e) { email = e; return this; }
public Builder phone(String p) { phone = p; return this; }
public Builder department(String d) { department = d; return this; }
public Builder gpa(double g) { gpa = g; return this; }
public Student build() {
if (name == null || name.isEmpty()) throw new IllegalArgumentException("Name required");
if (age < 16 || age > 30) throw new IllegalArgumentException("Age must be 16-30");
if (gpa != -1 && (gpa < 0 || gpa > 10)) throw new IllegalArgumentException("GPA must be 0-10");
return new Student(this);
}
}
public static void main(String[] args) {
Student s = new Student.Builder("Arjun", 20).email("arjun@email.com").gpa(8.5).build();
System.out.println(s);
try {
new Student.Builder("", 20).build();
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
}
}Challenge 4: Event System with Observer Pattern
HardBuild an EventBus that supports multiple event types (strings). Observers subscribe to specific event types. When an event is published, only observers subscribed to that type are notified. Support subscribe, unsubscribe, and publish.
Sample Input
Subscribe o1 to 'login', o2 to 'purchase', o3 to both. Publish 'login' and 'purchase'.
Sample Output
On login: o1 notified, o3 notified
On purchase: o2 notified, o3 notified
Use Observer pattern with Map<String, List<Observer>> for topic-based routing.
import java.util.*;
interface EventListener {
void onEvent(String eventType, String data);
}
class EventBus {
private Map<String, List<EventListener>> subscribers = new HashMap<>();
void subscribe(String event, EventListener listener) {
subscribers.computeIfAbsent(event, k -> new ArrayList<>()).add(listener);
}
void unsubscribe(String event, EventListener listener) {
List<EventListener> list = subscribers.get(event);
if (list != null) list.remove(listener);
}
void publish(String event, String data) {
List<EventListener> list = subscribers.getOrDefault(event, Collections.emptyList());
for (EventListener l : list) l.onEvent(event, data);
}
}
public class EventSystemChallenge {
public static void main(String[] args) {
EventBus bus = new EventBus();
EventListener authLogger = (type, data) -> System.out.println("AuthLog: [" + type + "] " + data);
EventListener purchaseTracker = (type, data) -> System.out.println("Purchase: [" + type + "] " + data);
EventListener allEvents = (type, data) -> System.out.println("Monitor: [" + type + "] " + data);
bus.subscribe("login", authLogger);
bus.subscribe("purchase", purchaseTracker);
bus.subscribe("login", allEvents);
bus.subscribe("purchase", allEvents);
System.out.println("--- Login Event ---");
bus.publish("login", "User Arjun logged in");
System.out.println("\n--- Purchase Event ---");
bus.publish("purchase", "Order #1234 placed");
}
}Challenge 5: Discount Strategy System
HardCreate a shopping discount system using the Strategy pattern. Implement three discount strategies: NoDiscount, PercentageDiscount(percent), and FlatDiscount(amount). A ShoppingCart should accept a discount strategy and calculate the final price after discount.
Sample Input
Cart with items totaling 1000. Apply 10% discount, then flat 200 discount.
Sample Output
No discount: 1000.00
10% discount: 900.00
Flat 200 discount: 800.00
Use Strategy pattern with a DiscountStrategy interface. Support switching strategies at runtime.
import java.util.*;
interface DiscountStrategy {
double applyDiscount(double total);
String describe();
}
class NoDiscount implements DiscountStrategy {
public double applyDiscount(double total) { return total; }
public String describe() { return "No discount"; }
}
class PercentageDiscount implements DiscountStrategy {
private double percent;
PercentageDiscount(double p) { percent = p; }
public double applyDiscount(double total) { return total * (1 - percent / 100); }
public String describe() { return percent + "% discount"; }
}
class FlatDiscount implements DiscountStrategy {
private double amount;
FlatDiscount(double a) { amount = a; }
public double applyDiscount(double total) { return Math.max(0, total - amount); }
public String describe() { return "Flat " + (int) amount + " discount"; }
}
class ShoppingCart {
private List<Double> items = new ArrayList<>();
private DiscountStrategy strategy = new NoDiscount();
void addItem(double price) { items.add(price); }
void setDiscount(DiscountStrategy s) { strategy = s; }
double checkout() {
double total = items.stream().mapToDouble(Double::doubleValue).sum();
double finalPrice = strategy.applyDiscount(total);
System.out.printf("%s: %.2f -> %.2f%n", strategy.describe(), total, finalPrice);
return finalPrice;
}
}
public class DiscountChallenge {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
cart.addItem(500); cart.addItem(300); cart.addItem(200);
cart.setDiscount(new NoDiscount());
cart.checkout();
cart.setDiscount(new PercentageDiscount(10));
cart.checkout();
cart.setDiscount(new FlatDiscount(200));
cart.checkout();
}
}Need to Review the Concepts?
Go back to the detailed notes for this chapter.
Read Chapter NotesWant to learn Java with a live mentor?
Explore our Java Masterclass