15 questions found
How would you use the Chain of Responsibility pattern to implement a request-processing pipeline (such as middleware-style validation/authentication/logging), and how does this relate conceptually to servlet filters or web framework middleware?
Advanced
Chain of Responsibility passes a request sequentially through a chain of independent handler objects, each deciding whether to process the request itself, pass it along to the next handler in the chain, or short-circuit the chain entirely (e.g., rejecting an unauthenticated request before it reaches business logic) -- this is conceptually exactly how servlet Filters, ASP.NET Core middleware, and similar web framework request pipelines work: each handler/filter/middleware component wraps and potentially short-circuits the remainder of the chain, letting cross-cutting concerns (auth, logging, rate limiting) be composed independently and reordered without modifying the core request-handling logic itself.
public abstract class Handler {
protected Handler next;
public Handler setNext(Handler next) { this.next = next; return next; }
public abstract void handle(Request request);
}
class AuthHandler extends Handler {
public void handle(Request request) {
if (!request.isAuthenticated()) { throw new SecurityException("Unauthorized"); }
if (next != null) next.handle(request); // pass along the chain
}
}
Handler chain = new AuthHandler();
chain.setNext(new LoggingHandler()).setNext(new BusinessLogicHandler());
chain.handle(incomingRequest);
Real-world example
A custom request-processing pipeline (built without a full web framework) chains an AuthHandler, RateLimitHandler, and LoggingHandler together using Chain of Responsibility, mirroring exactly the same conceptual structure as servlet Filters or ASP.NET Core middleware, letting each concern be independently composed, reordered, or removed.
Common follow-ups: What's the difference between Chain of Responsibility and simply calling a sequence of methods directly, given both process a request through multiple steps?;How would you make the chain configuration itself more flexible/dynamic (e.g., loaded from configuration rather than hardcoded)?
ASP.NET Core Middleware & Request Pipeline;Filters
What is the difference between composition and inheritance as ways to reuse code, and why does the design principle "favor composition over inheritance" generally guide modern Java design (including many design patterns themselves)?
Intermediate
Inheritance creates a tight, static "is-a" coupling between a subclass and its superclass (fixed at compile time, exposing all of a superclass's protected/public members and behavior, including behavior the subclass might not want, a problem known as the fragile base class problem where superclass changes can unexpectedly break subclasses); composition instead builds behavior by holding references to other objects (a "has-a" relationship) and delegating to them, which can be reconfigured at runtime, exposes only the specific behavior you choose to delegate, and avoids fragile base class issues entirely -- many design patterns (Strategy, Decorator, Composite) exist specifically as composition-based alternatives to what inheritance-based designs would otherwise attempt, which is why "favor composition over inheritance" is such a widely cited principle in object-oriented design.
// Inheritance-based (rigid, exposes ALL of Vehicle's behavior, fixed at compile time)
class Car extends Vehicle { }
// Composition-based (flexible, exposes only what Car chooses to delegate, reconfigurable at runtime)
class Car {
private final Engine engine; // composition: Car "has-a" Engine
public Car(Engine engine) { this.engine = engine; }
public void start() { engine.start(); } // delegates, exposing only what Car chooses to
}
Real-world example
A vehicle simulation initially modeled with deep inheritance hierarchies (ElectricCar extends Car extends Vehicle) is refactored to use composition instead (Car holds an Engine interface reference), letting the same Car class be reconfigured with different engine implementations at runtime and avoiding the fragile base class problems the original rigid inheritance hierarchy had caused as requirements evolved.
Common follow-ups: What specific problems does the 'fragile base class' issue cause in practice as a codebase evolves over time?;In what scenarios is inheritance still the genuinely correct and appropriate choice over composition?
OOP & Classes;Interfaces & Abstract Classes
What is the Factory Method pattern's simplest form -- a basic static factory returning different subtype instances based on input -- and why might you prefer it over exposing constructors directly?
Beginner
A simple factory encapsulates object creation logic behind a single method (often static) that decides which concrete subtype to instantiate based on input parameters, hiding the specific subclasses entirely from calling code -- this is useful when the calling code shouldn't need to know or care about the specific implementation class being used, only the common interface/supertype, and centralizes creation logic in one place rather than scattering constructor calls (and the subtype-selection logic they'd otherwise require) throughout the codebase.
public interface Shape { double area(); }
class Circle implements Shape { /* ... */ }
class Square implements Shape { /* ... */ }
public class ShapeFactory {
public static Shape create(String type, double size) {
return switch (type) {
case "circle" -> new Circle(size);
case "square" -> new Square(size);
default -> throw new IllegalArgumentException("Unknown shape: " + type);
};
}
}
Real-world example
A drawing application reads shape type names from a configuration file and uses a simple ShapeFactory.create() method to instantiate the correct Shape subtype, keeping the mapping between configuration strings and concrete classes centralized in one place rather than scattered across the codebase wherever a shape needs to be created.
Common follow-ups: How does this simple factory differ from the true Gang-of-Four Factory Method pattern involving subclass-overridden creation?;What happens to this design as the number of supported shape types grows very large?
Interfaces & Abstract Classes;OOP & Classes
How would you implement the Composite pattern to treat individual objects and groups of objects uniformly, such as a file system with files and directories?
Intermediate
Composite defines a common interface shared by both individual ("leaf") objects and composite ("container") objects that hold collections of the same interface type, letting client code treat a single object and a tree of nested objects uniformly through that shared interface -- a composite's operations (like calculating total size) typically recurse into its children, naturally handling arbitrarily deep nesting without the calling code needing to know whether it's dealing with a single leaf or an entire subtree.
public interface FileSystemNode { long getSize(); }
class File implements FileSystemNode {
private final long size;
public File(long size) { this.size = size; }
public long getSize() { return size; }
}
class Directory implements FileSystemNode {
private final List<FileSystemNode> children = new ArrayList<>();
public void add(FileSystemNode node) { children.add(node); }
public long getSize() {
return children.stream().mapToLong(FileSystemNode::getSize).sum(); // recurses uniformly
}
}
Real-world example
A file system browser calculates the total size of a deeply nested directory tree by calling getSize() on the top-level Directory, which transparently recurses through every nested File and Directory via the shared FileSystemNode interface, without the calling code needing any special-case logic for files versus directories.
Common follow-ups: How does the Composite pattern handle operations that only make sense for one type (leaf or composite) but not the other?;What's the performance consideration of a Composite structure that's extremely deeply nested?
OOP & Classes;Interfaces & Abstract Classes
How would you implement the Proxy pattern to add lazy initialization to an expensive-to-create object, and how does this differ structurally from the Decorator pattern despite both wrapping an object behind a shared interface?
Advanced
A Proxy implements the same interface as a real subject object, controlling access to it -- for lazy initialization specifically, the proxy defers actually creating the expensive real object until it's genuinely needed (the first real method call), transparently creating and delegating to it at that point -- structurally, Proxy and Decorator look nearly identical (both wrap an object behind a shared interface and delegate), but their INTENT differs: Proxy controls access to (or defers creation of) essentially one underlying object, while Decorator is specifically designed for composing multiple stacked behavior-adding layers onto an object that already exists.
public interface ExpensiveResource { void operation(); }
class RealExpensiveResource implements ExpensiveResource {
public RealExpensiveResource() { /* expensive initialization, e.g. loading a large file */ }
public void operation() { System.out.println("Operating"); }
}
class LazyProxy implements ExpensiveResource {
private RealExpensiveResource real;
public void operation() {
if (real == null) { real = new RealExpensiveResource(); } // created only on first actual use
real.operation();
}
}
Real-world example
A reporting application wraps an expensive-to-construct ReportGenerator (which loads a large dataset on construction) in a LazyProxy, deferring that expensive initialization until a report is actually requested, avoiding the upfront cost entirely for application instances that never end up generating a report at all.
Common follow-ups: What other purposes does the Proxy pattern serve beyond lazy initialization (remote proxies, protection proxies, caching proxies)?;How does Java's built-in java.lang.reflect.Proxy relate to this design pattern conceptually?
Design Patterns in Java;Caching