15 questions found
What is the Singleton pattern, and how would you implement a thread-safe Singleton in Java?
Beginner
Singleton ensures a class has exactly one instance and provides a global access point to it -- the enum-based approach is generally considered the simplest, safest way to implement it in Java (inherently thread-safe, serialization-safe, and immune to reflection-based instantiation attacks that can break other approaches), though the classic double-checked-locking lazy initialization pattern (using a volatile field) remains common for scenarios not suited to enum.
public enum ConfigManager {
INSTANCE;
private final Map<String, String> settings = new HashMap<>();
public String get(String key) { return settings.get(key); }
}
// Usage
ConfigManager.INSTANCE.get("apiUrl");
Real-world example
An application-wide configuration manager is implemented as an enum-based Singleton, guaranteeing exactly one instance exists across the entire application regardless of how many places reference ConfigManager.INSTANCE, with the JVM itself enforcing this guarantee rather than relying on manual synchronization logic.
Common follow-ups: Why is the enum-based Singleton considered immune to reflection-based attacks that can break the classic private-constructor approach?;What are the criticisms of Singleton as an anti-pattern, particularly regarding testability?
Concurrency & Threads;OOP & Classes
How does the Builder pattern solve the "telescoping constructor" problem, and how would you implement it for a class with many optional parameters?
Intermediate
The telescoping constructor problem arises when a class has many optional fields, leading to an unwieldy proliferation of overloaded constructors covering various combinations of provided parameters -- the Builder pattern instead uses a separate, often static nested Builder class with fluent setter-like methods (each returning `this` for chaining) that accumulate configuration before a final build() method constructs the actual immutable target object, giving readable, self-documenting object construction regardless of how many optional fields exist, without needing dozens of constructor overloads.
public class Pizza {
private final String size;
private final boolean cheese, pepperoni, mushroom;
private Pizza(Builder b) {
size = b.size; cheese = b.cheese; pepperoni = b.pepperoni; mushroom = b.mushroom;
}
public static class Builder {
private final String size;
private boolean cheese, pepperoni, mushroom;
public Builder(String size) { this.size = size; }
public Builder cheese(boolean val) { cheese = val; return this; }
public Builder pepperoni(boolean val) { pepperoni = val; return this; }
public Pizza build() { return new Pizza(this); }
}
}
Pizza pizza = new Pizza.Builder("large").cheese(true).pepperoni(true).build();
Real-world example
An HTTP client configuration class with a dozen optional settings (timeout, retry policy, headers, proxy, etc.) uses a Builder, letting callers set only the specific options relevant to them via readable chained method calls, avoiding both an unwieldy multi-parameter constructor and error-prone positional argument ordering.
Common follow-ups: How does Java's newer Record type relate to or compete with the Builder pattern for simpler cases?;What's the difference between this classic Builder pattern and Lombok's @Builder-generated equivalent?
Records & Sealed Classes;OOP & Classes
How would you implement the Observer pattern in Java, and how does it compare to using the built-in PropertyChangeSupport or a reactive streams library?
Advanced
The Observer pattern defines a one-to-many dependency where a subject notifies registered observers of state changes -- implementable manually with a List<Observer> and a notify loop, via the JDK's built-in (though somewhat dated) java.beans.PropertyChangeSupport for JavaBean-style property change notification, or more robustly via a reactive streams library (like RxJava or Project Reactor) providing composable operators (filtering, mapping, backpressure handling) far beyond a simple manual observer list, making the choice largely dependent on whether you need simple notification or a full reactive pipeline with transformation and backpressure semantics.
public interface Observer { void update(String event); }
public class EventPublisher {
private final List<Observer> observers = new ArrayList<>();
public void subscribe(Observer o) { observers.add(o); }
public void publish(String event) {
for (Observer o : observers) { o.update(event); }
}
}
EventPublisher publisher = new EventPublisher();
publisher.subscribe(event -> System.out.println("Received: " + event));
publisher.publish("orderPlaced");
Real-world example
A simple in-process event bus used to decouple order processing from notification/logging concerns implements a lightweight manual Observer pattern (since the use case doesn't need reactive streams' backpressure or complex operator chains), while a data pipeline handling high-volume, filtered, transformed event streams instead adopts Project Reactor for its much richer composability.
Common follow-ups: What are the memory leak risks of the Observer pattern if subscribers forget to unsubscribe (a 'lapsed listener' problem)?;How does the Observer pattern relate conceptually to the publish-subscribe messaging pattern used in distributed systems?
SignalR & Real-Time Communication;Concurrency & Threads
What is the Strategy pattern, and how do Java 8's functional interfaces and lambdas make implementing it significantly more concise than the traditional interface-plus-implementation-classes approach?
Intermediate
Strategy defines a family of interchangeable algorithms behind a common interface, letting the algorithm used by a context object be selected/swapped at runtime -- prior to Java 8, each strategy required its own separate implementation class; with functional interfaces and lambdas, a strategy can often be expressed inline as a lambda expression directly at the point of use, eliminating the boilerplate of defining a full named class for what's conceptually just a single behavior/algorithm, dramatically reducing the ceremony traditionally associated with this pattern.
public interface DiscountStrategy {
double apply(double price);
}
public class ShoppingCart {
private DiscountStrategy discount;
public void setDiscount(DiscountStrategy discount) { this.discount = discount; }
public double checkout(double total) { return discount.apply(total); }
}
ShoppingCart cart = new ShoppingCart();
cart.setDiscount(price -> price * 0.9); // 10% off, expressed as a lambda instead of a named class
double finalPrice = cart.checkout(100.0);
Real-world example
An e-commerce checkout system swaps between different discount calculation strategies (percentage off, flat amount off, buy-one-get-one) by passing different lambda expressions implementing the same DiscountStrategy functional interface, avoiding the need to define and instantiate a separate named class for each individual discount type.
Common follow-ups: What's the trade-off of using an inline lambda versus a named class implementation when the strategy logic becomes more complex?;How does the Strategy pattern differ conceptually from the Template Method pattern?
Functional Interfaces & Method References;Streams & Lambdas
How would you implement the Decorator pattern in Java, and how does java.io's InputStream/OutputStream class hierarchy exemplify this pattern in the JDK itself?
Advanced
Decorator lets you dynamically add responsibilities to an object by wrapping it in one or more decorator objects that implement the same interface as the original, each adding its own behavior before/after delegating to the wrapped object -- java.io is the JDK's own canonical real-world example: FileInputStream provides raw byte reading, and it can be wrapped in a BufferedInputStream (adds buffering) which can itself be wrapped in a GZIPInputStream (adds decompression), each layer transparently adding behavior while preserving the common InputStream interface, letting you compose exactly the combination of behaviors you need without an explosion of subclasses covering every possible combination.
InputStream raw = new FileInputStream("data.gz");
InputStream buffered = new BufferedInputStream(raw); // adds buffering
InputStream decompressed = new GZIPInputStream(buffered); // adds decompression
// Each layer implements InputStream and delegates to the wrapped stream,
// composing behaviors without needing a single "BufferedGZIPFileInputStream" class
Real-world example
A custom logging framework wraps a base Logger implementation in decorator layers adding timestamp formatting, then request-correlation-ID injection, then asynchronous batching, letting each concern be independently composed and tested rather than requiring one monolithic class handling all these responsibilities together.
Common follow-ups: Why does this pattern avoid the 'class explosion' problem that inheritance-based combination of behaviors would create?;How does Decorator differ structurally from the Proxy pattern, given both wrap an object behind the same interface?
I/O & NIO;OOP & Classes
What is the Factory Method pattern, and how does it differ from the simpler Static Factory Method idiom (like List.of() or Optional.of())?
Intermediate
The Factory Method design pattern (Gang of Four) defines an abstract method in a base class that subclasses override to determine which concrete type gets instantiated, deferring instantiation decisions to subclasses as part of a broader class hierarchy design; the more commonly used "static factory method" idiom (a simpler, unrelated convention popularized by Effective Java) is just a static method on a class that returns an instance of that class or a related type (like List.of() or Optional.of()), used primarily for more descriptive naming than a constructor, controlling instance caching/reuse, or returning a subtype without exposing it -- despite the similar name, these are genuinely distinct concepts serving different structural purposes.
// GoF Factory Method pattern: subclasses decide the concrete type
abstract class DocumentCreator {
abstract Document createDocument(); // subclasses override to return different Document types
}
class PdfCreator extends DocumentCreator {
Document createDocument() { return new PdfDocument(); }
}
// Static factory method idiom: just a descriptively-named static creation method
public class Point {
public static Point of(int x, int y) { return new Point(x, y); } // more readable than 'new Point(x, y)'
}
Real-world example
A document processing library uses the true GoF Factory Method pattern (an abstract createDocument() overridden by PdfCreator, WordCreator, etc.) to let each concrete creator subclass determine its own document type, while a separate, unrelated utility class simply exposes a static factory method Point.of(x, y) purely for more readable object construction syntax.
Common follow-ups: What are the specific benefits static factory methods have over public constructors, according to Effective Java?;When would you actually need the full GoF Factory Method pattern versus just a simpler static factory method?
OOP & Classes;Interfaces & Abstract Classes
How would you implement the Dependency Injection pattern manually (without a framework like Spring) using constructor injection, and what testability benefits does this provide?
Advanced
Manual constructor-based dependency injection means a class declares its dependencies as constructor parameters (rather than instantiating them directly inside the class or looking them up from a global registry/service locator), with the responsibility of actually providing those dependencies pushed to the caller (or, in a larger application, to a small "composition root" that wires everything together near the application's entry point) -- this decouples a class from concrete implementations of its dependencies, letting tests substitute mock/stub implementations trivially by passing them directly into the constructor, without needing any framework, reflection, or special test configuration.
public interface PaymentGateway { boolean charge(double amount); }
public class OrderService {
private final PaymentGateway gateway;
public OrderService(PaymentGateway gateway) { // dependency injected via constructor
this.gateway = gateway;
}
public boolean placeOrder(double total) { return gateway.charge(total); }
}
// In tests, trivially substitute a fake implementation, no framework needed
OrderService testService = new OrderService(amount -> true); // lambda implementing PaymentGateway
Real-world example
A unit test for OrderService constructs it directly with a hand-written fake PaymentGateway lambda that always returns true, testing OrderService's own logic in complete isolation from any real payment processing code or network calls, entirely without needing a DI framework or mocking library for this simple case.
Common follow-ups: What's the difference between constructor injection, setter injection, and field injection, and why is constructor injection generally preferred?;At what codebase size/complexity does manual DI wiring typically become unwieldy enough to justify adopting a framework like Spring?
Dependency Injection;Testing Strategy
What is the Adapter pattern, and when would you use it to make an existing class compatible with an interface it wasn't originally designed to implement?
Intermediate
Adapter wraps an existing class (with an incompatible interface) inside a new class that implements the interface your code actually needs, translating calls between the two -- commonly used when integrating a third-party library or legacy class whose API doesn't match what your code expects, letting you avoid modifying the original class (which you may not even have access to modify) while still making it usable wherever the target interface is expected.
// Third-party class with an incompatible interface you can't modify
class LegacyRectangle {
void oldDraw(int x1, int y1, int x2, int y2) { /* ... */ }
}
// Your application's expected interface
interface Shape { void draw(); }
// Adapter bridges the two
class RectangleAdapter implements Shape {
private final LegacyRectangle legacy;
private final int x, y, width, height;
public RectangleAdapter(LegacyRectangle legacy, int x, int y, int w, int h) {
this.legacy = legacy; this.x = x; this.y = y; this.width = w; this.height = h;
}
public void draw() { legacy.oldDraw(x, y, x + width, y + height); }
}
Real-world example
A modern application integrating a legacy third-party billing library (with an API predating the application's own PaymentProcessor interface) wraps the legacy library's class in an Adapter implementing PaymentProcessor, letting the rest of the codebase interact with it through the consistent modern interface without any changes to the legacy library itself.
Common follow-ups: How does the Adapter pattern differ from the Facade pattern, given both seem to 'wrap' existing functionality?;What's the difference between a class adapter (using inheritance) and an object adapter (using composition) approach?
Interfaces & Abstract Classes;OOP & Classes
How would you implement the Visitor pattern in Java to add new operations to an existing class hierarchy without modifying those classes, and how do Java's sealed classes and pattern matching (switch expressions) provide a modern alternative?
Advanced
The classic Visitor pattern uses double-dispatch (each element class has an accept(Visitor) method calling back into a corresponding visit(ConcreteElement) method on the visitor) to let you add new operations over a fixed set of element types without modifying those types themselves, at the cost of considerable boilerplate and difficulty adding new element types later; Java's sealed classes (restricting which classes can implement/extend an interface) combined with exhaustive pattern-matching switch expressions (Java 17+/21+) provide a much more concise, compiler-checked alternative for exactly this "closed set of types, add new operations" scenario, since the compiler can verify at compile time that a switch expression handles every possible sealed subtype, without any of the classic Visitor pattern's boilerplate.
// Modern alternative using sealed interfaces and pattern matching (Java 21+)
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double base, double height) implements Shape {}
double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> 0.5 * t.base() * t.height();
// compiler ERRORS if a new Shape subtype is added and this switch isn't updated -- exhaustiveness checking
};
}
Real-world example
A geometry library modeling shapes as a sealed hierarchy uses exhaustive pattern-matching switch expressions to implement operations like area() and perimeter(), gaining the same 'add new operations easily' benefit the classic Visitor pattern provided, but with compiler-enforced exhaustiveness checking and dramatically less boilerplate than hand-writing accept()/visit() methods across every class.
Common follow-ups: What specific scenario still favors the classic double-dispatch Visitor pattern over sealed classes and pattern matching?;How does sealed class exhaustiveness checking actually work at compile time?
Records & Sealed Classes;Pattern Matching & Switch Expressions
What is the Template Method pattern, and how is it used to define the skeleton of an algorithm while letting subclasses customize specific steps?
Intermediate
Template Method defines the overall structure/sequence of an algorithm in a base class's (typically final) method, calling out to one or more abstract or overridable "hook" methods that subclasses implement to customize specific steps, without subclasses being able to alter the overall algorithm's sequence itself -- this is a common pattern in framework design, where the framework controls the overall flow (like a test runner's setup-execute-teardown sequence) while application code plugs in the specific behavior for individual steps.
public abstract class DataProcessor {
public final void process() { // template method -- final, defines fixed algorithm structure
loadData();
validate();
transform();
saveResults();
}
protected abstract void loadData();
protected abstract void transform();
protected void validate() { /* default implementation, can be overridden */ }
protected void saveResults() { System.out.println("Saving..."); }
}
class CsvProcessor extends DataProcessor {
protected void loadData() { /* CSV-specific loading */ }
protected void transform() { /* CSV-specific transformation */ }
}
Real-world example
A test framework's base test class defines a final runTest() template method calling setUp(), executeTest(), and tearDown() in a fixed sequence, with individual test classes only needing to override the specific steps relevant to them, guaranteeing every test consistently follows the same overall lifecycle regardless of what each individual test actually does.
Common follow-ups: How does Template Method differ from the Strategy pattern, given both involve customizable behavior?;Why is marking the template method itself as final considered important to preserving the pattern's intent?
OOP & Classes;Testing Strategy