OOP Interview Questions in Java (With Code & Traps) — Freshers to Senior
A senior-level Java OOP interview guide covering the four pillars with mechanics, runtime vs compile-time polymorphism, method hiding, field shadowing, exact overriding/overloading rules, interfaces vs abstract classes, the diamond problem, SOLID principles with code, and object-model traps.
OOP questions look easy until the interviewer stops asking "what are the four pillars?" and starts asking "why does this field print the parent's value even though the object is a Child?" This is a senior-level OOP interview guide: not textbook definitions, but the mechanics interviewers actually probe — dynamic dispatch, method hiding vs overriding, the exact overriding/overloading rules, how Java dodges the diamond problem, SOLID with real code, and the field-shadowing and static-method traps that expose whether you understand the object model or just memorised it.
The four pillars — with the mechanics
Q1. Explain the four pillars, and which is a compile-time vs runtime concern.
| Pillar | What it buys you | Java mechanism |
|---|---|---|
| Encapsulation | Hide state, expose behaviour; enforce invariants | private fields + getters/setters/validation |
| Abstraction | Expose what, hide how | interface / abstract class |
| Inheritance | Reuse + IS-A specialisation | extends (single) / implements (multiple) |
| Polymorphism | One interface, many behaviours | Overriding = runtime; overloading = compile-time |
Encapsulation vs abstraction (the trap): encapsulation is about bundling and protecting data; abstraction is about hiding complexity behind a simpler contract. Encapsulation is a means; abstraction is the design goal.
Polymorphism & dynamic dispatch — the heart of OOP rounds
Q2. Overriding vs overloading — and when is each resolved?
Overloading (same name, different parameter list) is resolved by the compiler using the static/declared type of the arguments — this is static (compile-time) binding. Overriding (subclass redefines a superclass method with the same signature) is resolved by the JVM at runtime using the actual object type — dynamic (runtime) binding, implemented via a per-class method table (vtable).
Q3. Predict the output — method hiding vs overriding.
class Parent { static String who() { return "Parent.static"; } String hi() { return "Parent.hi"; } } class Child extends Parent { static String who() { return "Child.static"; } // HIDES, not overrides @Override String hi() { return "Child.hi"; } // overrides } Parent p = new Child(); System.out.println(p.who()); // "Parent.static" — static resolved on DECLARED type System.out.println(p.hi()); // "Child.hi" — instance resolved on ACTUAL type
Why: static methods are not polymorphic — they are hidden, bound at compile time to the reference's type. Only instance methods are dynamically dispatched. This single example is the most common OOP trap in senior interviews.
Q4. Predict the output — field shadowing.
class A { int x = 10; } class B extends A { int x = 20; } A ref = new B(); System.out.println(ref.x); // 10 — fields are NOT polymorphic (bound to declared type)
Fields are resolved at compile time by the reference type; methods at runtime by the object type. Never rely on field access through a superclass reference.
Q5. What are the exact rules for a valid override?
- Same name + same parameter list (signature). Different params = overload, not override.
- Return type must be the same or a covariant (narrower) subtype.
- Access modifier can only widen, never narrow (a
publicmethod can't be overridden asprotected). - Checked exceptions can only be the same or narrower; you may drop them or throw fewer/subtypes, but not broader checked exceptions.
private,static, andfinalmethods cannot be overridden (private/static are hidden; final is sealed).
Q6. How is overloading resolved when multiple candidates match?
The compiler picks the most specific applicable method in three phases: (1) exact/widening primitive match, (2) autoboxing/unboxing, (3) varargs — earlier phases win.
void f(long x) {} // widening void f(Integer x) {} // boxing void f(int... x) {} // varargs f(5); // calls f(long) — widening beats boxing beats varargs
Abstract class vs interface (modern Java)
Q7. When would you choose one over the other?
| Abstract class | Interface (Java 8+) | |
|---|---|---|
| Multiple inheritance | No (single extends) | Yes (many implements) |
| State (instance fields) | Yes | No (only static final constants) |
| Method bodies | Concrete + abstract | default/static (Java 8), private (Java 9) |
| Constructor | Yes (runs on subclass init) | No |
Rule of thumb: use an interface to define a capability/contract ("can be compared", "can be closed") that unrelated types share; use an abstract class when subclasses share state and partial implementation (a true IS-A hierarchy). Since Java 8 gave interfaces default methods, the gap narrowed — but only abstract classes hold mutable state.
Q8. If interfaces have default methods, is there a diamond problem? How does Java resolve it?
interface A { default String hi() { return "A"; } } interface B { default String hi() { return "B"; } } class C implements A, B { @Override public String hi() { return A.super.hi(); // MUST disambiguate explicitly } }
Java forces the implementing class to override and pick a winner with InterfaceName.super.method() — a compile error otherwise. Java sidesteps the classic C++ diamond because interfaces carry behaviour but no state, so there's no ambiguous field to inherit twice — that's the real reason Java allows multiple inheritance of type but not of state.
SOLID — name them, and show the fix
| Principle | One-line rule | Smell it fixes |
|---|---|---|
| SRP | One reason to change per class | God class doing parsing + IO + business rules |
| OCP | Open to extend, closed to modify | A growing if/switch on a type code |
| LSP | Subtypes must be substitutable | Square extends Rectangle, breaks setWidth |
| ISP | Many small interfaces > one fat one | Implementors forced to stub unused methods |
| DIP | Depend on abstractions, not concretes | new MySqlRepo() hardcoded in a service |
The Open/Closed fix in one snippet — replace a type switch with polymorphism (this is also the Strategy pattern):
// BEFORE: every new shape edits this method (violates OCP) double area(Shape s) { if (s.type == CIRCLE) return Math.PI*s.r*s.r; else if (s.type == SQUARE) return s.side*s.side; ... } // AFTER: each shape owns its area(); adding a shape edits nothing interface Shape { double area(); } class Circle implements Shape { public double area(){ return Math.PI*r*r; } }
Inheritance vs composition, and the relationships
Q9. "Favour composition over inheritance" — why?
Inheritance is a compile-time, tightly-coupled IS-A bond: the subclass depends on the superclass's internals, and a superclass change can silently break subclasses (the "fragile base class" problem). Composition (HAS-A) injects behaviour via a field/interface, giving runtime flexibility, easier testing (mock the collaborator), and no exposure to a parent's implementation. Rule: use inheritance only for a genuine, stable IS-A; otherwise compose.
Q10. Association vs aggregation vs composition?
Association — any relationship ("a Teacher teaches Students"). Aggregation — a weak HAS-A where the part outlives the whole (a Department HAS Employees, but employees exist without it). Composition — a strong HAS-A where the part's lifecycle is owned by the whole (a House HAS Rooms; destroy the house, the rooms die). Composition = ownership; aggregation = reference.
Object model mechanics
Q11. Constructor chaining — what runs, in what order?
// On 'new Child()': // 1. Child ctor's implicit/explicit super(...) runs FIRST // 2. Parent's field initialisers + Parent ctor body // 3. Child's field initialisers + Child ctor body // super() or this() MUST be the first statement in a ctor.
Every constructor calls super() (implicitly if you don't) before its own body — so the base object is fully built before the subclass touches it. Calling an overridable method from a constructor is a bug: it dispatches to the subclass override before the subclass fields are initialised.
Q12. Why does Java not support multiple inheritance of classes?
To avoid the diamond ambiguity of state: if C extended both A and B which both extended Object with a field x, which x does C get? Java allows multiple inheritance of type (interfaces) because they hold no instance state, and forces explicit resolution for clashing default methods (Q8).
Q13. Upcasting, downcasting, and ClassCastException.
Animal a = new Dog(); // upcast — always safe, implicit Dog d = (Dog) a; // downcast — checked at RUNTIME Cat c = (Cat) a; // ClassCastException — a is really a Dog if (a instanceof Dog dog) dog.bark(); // pattern matching (Java 16+) — safe
Rapid-fire
- Can a constructor be
final/static/overridden? No to all — constructors aren't inherited, so those modifiers are meaningless. - Can you override a
staticmethod? No — you hide it (Q3). - Abstract class with no abstract methods? Legal — it just can't be instantiated directly.
- Interface with a
mainmethod? Legal since Java 8 (staticmethods allowed) — it runs. - Marker interface? An empty interface (
Serializable,Cloneable) used as a runtime type tag. - Cohesion vs coupling? High cohesion (a class does one thing well) + low coupling (few dependencies) = maintainable design.
Go deeper by topic
- Core Java interview questions — the backbone set (JVM, Strings, equals/hashCode, generics)
- Collections interview questions — HashMap internals, fail-fast, ConcurrentHashMap
- Multithreading interview questions — JMM, volatile, locks
- Exception interview questions — checked vs unchecked, try-with-resources
- Spring Boot interview questions — IoC/DI applies DIP in practice
Here's the shift that got me through senior OOP rounds: I stopped treating the four pillars as the answer and started treating them as the question. An interviewer who asks "what is polymorphism?" is really asking "do you know that fields and static methods aren't polymorphic, and can you tell me why?" So for every concept on this page, I forced myself to produce the one snippet that would surprise a junior — the field that prints the parent's value, the static method that ignores the object type, the diamond that won't compile. Once you can generate those from the object model instead of recalling them, no OOP follow-up can corner you. Learn the mechanics behind the pillars, not just their names, and you'll reason your way through anything they throw. You've got this.
Related
← Core Java interview questions · Collections interview questions →