Design Patterns
Common design patterns in Java — singleton, factory, builder, and dependency injection.
11 questions
MiddleTheoryVery commonWhy should you favour composition over inheritance in Java?
Why should you favour composition over inheritance in Java?
Inheritance is white-box reuse: a subclass leans on the superclass's internals, so a change upstream can silently break it — the fragile-base-class problem — and the bond is fixed at compile time. Composition is black-box reuse: an object holds a collaborator and delegates through its interface, so behaviour can be swapped at runtime, stubbed in a test, and combined freely. Keep inheritance for a genuine "is-a" over a stable base.
Common mistakes
- ✗Extending a class just to reuse a couple of its methods, when delegation would do
- ✗Assuming a subclass is safe from superclass changes as long as it compiles
- ✗Modelling every relationship as "is-a" and ending with one subclass per feature combination
Follow-up questions
- →What exactly is the fragile-base-class problem, and how does delegation avoid it?
- →When is inheritance still the right call over holding a collaborator?
MiddleTheoryVery commonWhat does the creational singleton pattern guarantee, and how do you make it thread-safe?
What does the creational singleton pattern guarantee, and how do you make it thread-safe?
The creational singleton pattern guarantees a class has exactly one instance and gives a global access point to it: a private constructor blocks outside new, and a static field plus accessor hand out that lone instance. Make it thread-safe without races via an enum (the JVM serializes its initialization), a static holder class (lazy, loaded on first access), or double-checked locking on a volatile field.
Common mistakes
- ✗Lazy-initializing without synchronization, letting two threads each create an instance under a race
- ✗Forgetting
volatilein double-checked locking, exposing a partially constructed object - ✗Ignoring that serialization or reflection can break a naive singleton's single-instance guarantee
Follow-up questions
- →Why is an
enumsingleton also safe against serialization and reflection attacks? - →What exactly does the
volatilekeyword prevent in double-checked locking?
MiddleTheoryCommonWhat does the creational builder pattern solve, and when do you reach for it?
What does the creational builder pattern solve, and when do you reach for it?
The creational builder pattern constructs a complex object step by step through a fluent API — chained setter-like calls ending in build() — instead of one giant constructor. It eliminates telescoping constructors, where overloads with long, same-typed parameter lists turn unreadable. Reach for it when an object has many optional parameters, or to build an immutable object — the builder gathers fields, then build() returns the result.
Common mistakes
- ✗Leaving the built object mutable, defeating the pattern's immutability benefit
- ✗Using a builder for a trivial two-field object where a plain constructor is clearer
- ✗Confusing builder (creational) with decorator, which wraps to add behavior
Follow-up questions
- →How does a builder help enforce required fields versus optional ones?
- →Why does the builder pattern pair naturally with immutable objects?
MiddleTheoryCommonWhat problem does the creational factory pattern solve, and how does it decouple the client?
What problem does the creational factory pattern solve, and how does it decouple the client?
The creational factory pattern delegates object creation to a dedicated method or subclass that returns an interface or abstract type, instead of the client calling new on a concrete class. The client asks the factory for the abstract type and stays unaware of which implementation it gets, so swapping or adding implementations touches one place. It also centralizes construction logic in that single method.
Common mistakes
- ✗Returning a concrete type from the factory, so the client still depends on implementations
- ✗Confusing factory (creational) with a behavioral pattern about object interaction
- ✗Scattering
newcalls across the codebase instead of centralizing them in the factory
Follow-up questions
- →How does factory method differ from abstract factory?
- →How does returning an interface support the dependency inversion principle?
MiddleCodeCommonImplement the behavioral pattern Strategy with a functional interface
Implement the behavioral pattern Strategy with a functional interface
Give DiscountRule exactly one abstract method — long apply(long amountCents) — so any lambda or method reference is a strategy. Checkout takes the rule in its constructor, keeps it in a final field and delegates: total() returns Math.max(0, rule.apply(amountCents)). A new rule is just a new lambda at the call site, so Checkout never changes.
Common mistakes
- ✗Adding a second abstract method to the interface, which stops it being a lambda target
- ✗Subclassing
Checkoutonce per rule instead of holding the rule as a field - ✗Switching on an enum inside
total(), so every new rule has to editCheckout
Follow-up questions
- →Why does a second abstract method stop
DiscountRulefrom being usable as a lambda? - →How does the pattern
Strategydiffer from the patternTemplate Methodhere?
SeniorTheoryCommonWhat is dependency injection, and how does it relate to inversion of control?
What is dependency injection, and how does it relate to inversion of control?
Dependency injection supplies an object's collaborators from the outside — through its constructor or a setter — instead of the object building them itself with new. That inverts control: the object no longer decides which concrete dependency to create, so it depends on abstractions while an external assembler (often Spring) wires the graph. The payoff is looser coupling and easier testing — you inject a mock and swap implementations freely.
Common mistakes
- ✗Equating DI with a framework, when it is just passing dependencies in from outside
- ✗Calling
newon dependencies inside the class, which keeps control and defeats the point - ✗Depending on a concrete type instead of an abstraction, blocking substitution in tests
Follow-up questions
- →What are the trade-offs between constructor injection and setter injection?
- →How does an IoC container like Spring resolve and wire the dependency graph?
JuniorTheoryOccasionalWhat is a design pattern, and what are the three GoF categories?
What is a design pattern, and what are the three GoF categories?
A design pattern is a reusable, named solution to a recurring design problem — a proven template you adapt to your code, not a finished library. The Gang of Four groups them into creational patterns (object creation, e.g. singleton, factory), structural patterns (how objects compose, e.g. adapter, decorator), and behavioral patterns (how objects interact and share responsibility, e.g. observer, strategy).
Common mistakes
- ✗Treating a pattern as ready-made code to import rather than a template you adapt to context
- ✗Confusing the three categories — placing factory under structural instead of creational
- ✗Overusing patterns where plain code would be simpler, adding needless indirection
Follow-up questions
- →Give one behavioral pattern and explain the responsibility it distributes.
- →Why is a pattern a template rather than reusable library code?
MiddleTheoryOccasionalHow does the structural pattern Decorator shape the java.io classes?
How does the structural pattern Decorator shape the java.io classes?
A decorator wraps an object in another object of the same interface, adds behaviour and delegates the rest to the one it holds — so features stack at runtime instead of a subclass per combination. java.io is built this way: in new BufferedReader(new InputStreamReader(System.in)) each wrapper keeps the wrapped stream as a field and forwards read() to it, so the stages compose in any order.
Common mistakes
- ✗Thinking
BufferedReaderextends the reader it wraps rather than holding it as a field - ✗Confusing a decorator with an adapter — a decorator keeps the interface, an adapter changes it
- ✗Assuming the nesting order is irrelevant, when it decides in which order the stages run
Follow-up questions
- →Why does closing the outermost
java.iowrapper also close every stream underneath it? - →How does a decorator differ from the structural pattern
Adapter?
MiddleTheoryOccasionalHow does the behavioral pattern Observer show up in Java listeners?
How does the behavioral pattern Observer show up in Java listeners?
A subject keeps a list of registered listeners and pushes an event to each one when its state changes, so it knows only the listener interface, never the concrete classes, and observers can be added or removed at runtime. Swing's addActionListener, PropertyChangeSupport and Spring's ApplicationListener all have this shape; the subject holds its observers by composition, while the old java.util.Observable class was deprecated in Java 9 for forcing inheritance instead.
Common mistakes
- ✗Letting the subject depend on concrete listener classes instead of the listener interface
- ✗Forgetting to deregister a listener, so the subject's list keeps the observer alive and leaks it
- ✗Reaching for the deprecated
java.util.Observableinstead of a listener interface
Follow-up questions
- →How do you notify listeners safely when one of them deregisters during the notification loop?
- →Why was the class
java.util.Observabledeprecated in favour of listener interfaces?
MiddleTheoryOccasionalWhat does the behavioral pattern Template Method fix in a hierarchy?
What does the behavioral pattern Template Method fix in a hierarchy?
A method in the base class fixes the algorithm's skeleton — the order of the steps — and delegates the varying steps to abstract or overridable hooks that subclasses fill in; the skeleton is usually final so nobody can reorder it. HttpServlet.service() dispatching to doGet is the classic case: the base owns the invariant flow and calls down, and the subclass supplies only the details.
Common mistakes
- ✗Leaving the skeleton method overridable, so a subclass can reorder or skip the steps
- ✗Making every hook
abstractwhen an optional step should have a default empty body - ✗Confusing it with
Strategy— this one varies by inheritance,Strategyby composition
Follow-up questions
- →Why is the skeleton method usually declared
finalin the base class? - →How does the pattern
Template Methoddiffer from the patternStrategy?
SeniorTheoryOccasionalHow do the creational patterns Factory Method and Abstract Factory differ?
How do the creational patterns Factory Method and Abstract Factory differ?
Factory Method is a single overridable method that creates one product: a subclass of the creator picks the concrete class, so it varies by inheritance and is fixed once the subclass is chosen. Abstract Factory is an object with several creation methods producing a whole family of mutually consistent products; the client holds it by composition, so the entire family can be swapped at runtime.
Common mistakes
- ✗Calling any
statichelper that hidesnewanAbstract Factory— that is a static factory method - ✗Missing that
Abstract Factoryexists to keep a family of related products mutually consistent - ✗Assuming both vary by inheritance, when the client composes an
Abstract Factoryand swaps it at runtime
Follow-up questions
- →What breaks in an
Abstract Factorywhen you need to add a new product to the family? - →How does a static factory method differ from the pattern
Factory Method?