Composition vs Inheritance in Java
Fast recall
Quick revision
Core answer
Dog extends Animal. Composition builds a class from collaborators: Car holds an Engine. Composition often reduces dependence on a base class and allows collaborators to be replaced, but it is not automatically loose coupling—the field type and exposed behavior still decide the coupling.Whether you know the IS-A vs HAS-A test and why composition is usually the safer default.
Reaching for inheritance just to reuse code, creating fragile deep hierarchies and breaking the IS-A test.
Explain the fragile base class problem and that composition lets you swap behaviour at runtime (strategy pattern).
Interview-ready guide
Interview answer
Inheritance models an is-a relationship. If Dog extends Animal, code that accepts an Animal should be able to use a Dog without special cases. Composition models collaboration: a Car has an Engine field and delegates engine work to it.
I usually choose composition when behavior should be optional, replaceable, or combined from several collaborators. For example, a CheckoutService that receives a PaymentGateway interface can use a test gateway or a production gateway without subclassing the service. That is loose coupling because of the small interface and injection—not merely because a field exists. I choose inheritance when the parent defines a stable subtype contract and subclassing is intentionally supported. The guideline is to avoid extending a class only to borrow code, because superclass changes can affect subclasses in ways composition can contain.
Composition vs Inheritance in Java
Direct Answer
Inheritance creates a subtype relationship: Dog extends Animal. Composition builds a class from collaborators: Car holds an Engine. Composition often reduces dependence on a base class and allows collaborators to be replaced, but it is not automatically loose coupling—the field type and exposed behavior still decide the coupling.
Code Example
// BAD: Inheritance misused — Queue is NOT a Stack (IS-A fails)
class BadQueue extends java.util.Stack<Integer> {
void enqueue(int x) { push(x); } // borrows Stack.push
int dequeue() { return remove(0); }
// Problem: callers can also call inherited pop(), peek(), search() —
// all of which break queue semantics. The IS-A test failed.
}
// GOOD: Composition — Queue HAS-A Deque, delegates only what's needed
class Queue {
private final Deque<Integer> data = new ArrayDeque<>(); // HAS-A
void enqueue(int x) { data.addLast(x); } // delegate
int dequeue() { return data.removeFirst(); } // delegate
int size() { return data.size(); }
// The backing store is private — callers see only queue operations.
// Swap ArrayDeque for another Deque implementation anytime.
}
// BONUS: Composition enables runtime behaviour swapping (Strategy pattern)
interface SortStrategy {
void sort(int[] arr);
}
class Sorter {
private SortStrategy strategy; // HAS-A (injected)
Sorter(SortStrategy s) { this.strategy = s; }
void sort(int[] arr) { strategy.sort(arr); }
void setStrategy(SortStrategy s) { this.strategy = s; } // swap at runtime!
}
Common Mistakes & Pitfalls
- Reaching for
extendspurely to reuse code — inheritance without a genuine IS-A relationship couples you to the parent's internals and leaks its entire public API to your callers. - Skipping the IS-A test —
class Queue extends Stacklooks convenient, but a queue is not a stack; callers can call inheritedpop()or insert in the middle and break the queue's contract. - Ignoring Java's single-parent limit — once you
extendsone class for reuse, a second shared behavior forces an awkward rewrite, whereas composition lets you hold as many fields as you need. - Assuming a base-class change is safe — editing a parent method can silently break every subclass that relied on its internals; this is the fragile base class problem that composition sidesteps.
Check your understanding
Answer these without looking back at the article.
- What is the fragile base class problem?
- How does dependency injection use composition?
- When is inheritance definitely the right choice?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.