JavaOOPintermediate
Updated:

Java OOP Interview Questions and Answers

12 min read

OOP questions open almost every Java interview. Here are the 14 that actually get asked — with the mechanism-level answers and follow-up traps for each.

TL;DR – Quick Answer

Java OOP interviews revolve around a stable core: the four pillars, abstraction vs encapsulation, overloading vs overriding rules, how dynamic dispatch works, why Java bans multiple class inheritance, the diamond problem with default methods, constructor chaining, method hiding, SOLID principles and pass-by-value semantics. Interviewers grade you on mechanisms and code-level consequences, not textbook definitions.

On This Page

Why interviewers ask about OOP

OOP is the opening gambit of nearly every Java interview because it is the language's organizing idea — every framework on your resume (Spring, Hibernate, JUnit) is built on interfaces, proxies and polymorphism. If your OOP is shaky, nothing above it can be solid, and interviewers know it.

The questions also scale with experience: the same "composition vs inheritance" prompt filters a fresher (definitions), a mid-level developer (the decision rule) and a senior (a refactoring story). Expect OOP early in the round — your answers set the interviewer's difficulty dial for everything that follows.

If any definition below feels rusty, refresh with the four pillars explained before drilling the questions.

How to answer in an interview

For every OOP concept, prepare three layers: the definition (one sentence), the mechanism (what the compiler or JVM actually does), and a consequence (a bug or design outcome it causes). "Overriding is runtime polymorphism" is layer one; "the JVM dispatches on the object's actual type" is layer two; "that is why breaking LSP corrupts callers silently" is layer three.

Use original examples — Payment/UpiPayment beats the thousandth Animal/Dog. A domain example signals you have translated the concept into working code.

Q1. Explain the four pillars of OOP in one minute.

Encapsulation: bundle state with the methods that guard it, hide the fields. Abstraction: expose what a type does, hide how. Inheritance: an is-a relationship that reuses and specializes a base type. Polymorphism: one reference type, many runtime behaviors.

Practice this as a timed answer, attaching one Java feature to each pillar: private fields + getters, interfaces, extends, overriding. The differentiator is connecting them: encapsulation makes abstraction trustworthy, abstraction makes polymorphism useful, and inheritance is one (not the only) way to get polymorphism.

Interview note: Trap: "which pillar matters most in real codebases?" There is no canonical answer — but arguing for encapsulation (it localizes change) or polymorphism (it removes conditionals) with a reason beats naming all four again.

Q2. Abstraction vs encapsulation — aren't they the same thing?

No. Abstraction is the caller's view: expose an essential contract, hide the mechanism. Encapsulation is the implementer's protection: restrict access to internal state so invariants can't be broken from outside. Abstraction hides complexity; encapsulation hides data.

A concrete pairing makes it stick: sort(List) is abstraction — you neither know nor care that it is TimSort underneath. The private modCount field inside ArrayList is encapsulation — you cannot touch it, because external writes would corrupt iteration. A class with public mutable fields can never offer a stable abstraction.

Interview note: Follow-up: "show me encapsulation failing." A getter returning an internal mutable list — callers mutate your state without ever calling a setter. Access modifiers alone are not encapsulation.

Q3. What are the exact rules for overloading vs overriding?

Overloading: same name, different parameter list, resolved at compile time — return type alone cannot distinguish overloads. Overriding: same signature in a subclass, resolved at runtime by the object's actual type; the override may widen access, return a covariant type, and may not throw broader checked exceptions.

The rules interviewers test: an override cannot reduce visibility, cannot add new checked exceptions, and should carry @Override so the compiler catches accidental overloads — your defense against the classic equals(Object) vs equals(MyType) mistake.

class Base { protected Number value() throws IOException { ... } }
class Sub extends Base {
    @Override
    public Integer value() { return 42; }  // wider access, covariant, fewer throws: legal
}

Every relaxation is legal in exactly one direction — widen access, narrow return, narrow throws.

Interview note: Trap: "is this overriding or overloading: child has method(long), parent has method(int)?" Overloading — the signatures differ. Half of candidates answer overriding and then mispredict which method a call picks.

Q4. How does runtime polymorphism actually work in the JVM?

The compiler only verifies a method exists on the reference type; at runtime the JVM dispatches through the actual object's class — conceptually a virtual method table lookup — so the most-derived override always runs. This is dynamic dispatch, and it applies to instance methods only.

Payment p = new UpiPayment();     // reference type: Payment
p.process();                      // runs UpiPayment.process() at runtime

The three exclusions matter more than the happy path: static methods dispatch on the reference type, private methods are not inherited at all, and fields are never polymorphic — a base-reference field access reads the base field even if the child re-declares it.

Full dispatch walkthrough: compile-time vs runtime polymorphism.

Interview note: Follow-up: "parent reference, child object — parent field or child field?" Parent's. Field access is resolved statically by reference type; only instance methods dispatch dynamically. This single question eliminates a surprising number of candidates.

Q5. Why doesn't Java support multiple inheritance of classes, and how do default methods reintroduce the diamond problem?

Java bans multiple class inheritance to avoid ambiguity: two parents could supply different implementations and different state for the same member. Interfaces were safe because they carried no implementation — until Java 8 default methods brought back a controlled diamond: inherit the same default from two interfaces and the compiler forces you to resolve it.

interface Logger  { default String tag() { return "LOG"; } }
interface Auditor { default String tag() { return "AUDIT"; } }

class Service implements Logger, Auditor {
    @Override
    public String tag() {                 // compiler demands this override
        return Logger.super.tag();        // explicit choice
    }
}

The Logger.super.tag() syntax is the detail worth producing unprompted. The precedence rule: a concrete method from the class hierarchy always beats any interface default — classes win.

Interview note: Trap: "so interfaces now support multiple inheritance of behavior — of state too?" No. Interfaces still cannot declare instance fields, and that is precisely why the diamond stays resolvable.

Q6. Composition vs inheritance — give me the decision rule.

Inheritance for a genuine, permanent is-a relationship where you want the parent's full contract and polymorphic substitution. Composition for capability reuse — hold a reference, delegate, expose only what you choose. Default to composition: it couples you to a small interface, not a whole hierarchy, and can change at runtime.

The two-question test: would substituting the child for the parent always be correct (LSP, Q11)? And do you want all of the parent's public methods on your API? Two yeses justify extends; one no means compose — you cannot partially inherit. The cautionary tale is in the JDK: Stack extends Vector exposes insertElementAt on a stack, breaking its own LIFO invariant.

Interview note: Follow-up: "how do you keep polymorphism if you drop inheritance?" Implement an interface and delegate — composition plus interfaces gives substitutability without implementation coupling. That sentence is the complete senior answer.

Q7. Explain constructor chaining with this() and super().

Every constructor's first statement is either this(...) (a sibling constructor) or super(...) (a parent constructor) — write neither and the compiler inserts super(). Construction therefore always runs top-down: Object first, then each class down the hierarchy, then your constructor body.

The compile error everyone eventually hits: the parent has no no-arg constructor and the implicit super() finds nothing to call — which is why adding a parameterized constructor can break subclasses.

class Account {
    Account(String id) { ... }             // no no-arg constructor anymore
}
class SavingsAccount extends Account {
    SavingsAccount() { super("NEW"); }     // mandatory explicit super call
}

Bonus depth: calling an overridable method from a constructor is dangerous — the child's override runs before the child's fields are initialized, observing half-constructed state.

Interview note: Trap: "can a constructor be private, final, or inherited?" Private yes (singletons, factories), final no (constructors are never inherited or overridden — final is meaningless), inherited never.

Q8. Can you override static, private, or final methods?

No to all three, for different reasons. static methods are hidden, not overridden — a same-signature child static shadows the parent's, dispatched by reference type. private methods are invisible to subclasses, so a same-name child method is a new, unrelated method. final is an explicit compiler-enforced ban.

Method hiding is the demonstrable one:

class Parent { static String who() { return "parent"; } }
class Child extends Parent { static String who() { return "child"; } }

Parent p = new Child();
p.who();          // "parent" — static dispatch follows the REFERENCE type

If who() were an instance method, this would print "child". That single contrast is the cleanest proof of the static-vs-dynamic dispatch divide.

Interview note: Follow-up: "why would you declare a method final?" To protect an invariant that overrides could break — template methods, security checks, or anything called from a constructor. "Performance" is a legacy answer; the JIT devirtualizes regardless.

Q9. What are covariant return types?

Since Java 5, an override may return a subtype of the parent method's return type. Callers through the base reference still receive what the contract promised — just more specific — so substitutability is preserved while child-type callers avoid casting.

class Shape    { Shape copy() { ... } }
class Circle extends Shape {
    @Override
    Circle copy() { ... }               // covariant: Circle IS-A Shape
}
Circle c2 = new Circle().copy();        // no cast needed

The canonical uses are clone() overrides and fluent builders, where each subclass returns its own type. Note the asymmetry: return types may narrow, but parameter types may not change at all — change one and you have overloaded, not overridden. Covariance applies to reference types only, never primitives.

Interview note: Trap: "could an override return a supertype instead?" No — callers of the base contract would receive something weaker than promised. Covariance only tightens, never loosens.

Q10. Association vs aggregation vs composition — do these distinctions matter in Java?

All three are has-a relationships distinguished by ownership and lifecycle. Association: objects merely know each other (doctor–patient). Aggregation: parts outlive the whole (team–players). Composition: the whole owns its parts' lifecycles (house–rooms). Java has no keyword for any of them — the distinction lives in who creates, shares and releases the part.

In code, composition means the whole instantiates the part internally and never leaks the reference; aggregation means the part arrives via constructor or setter and may be shared. The distinction drives real decisions: composition parts can be mutable internals; aggregated parts shared across owners had better be immutable or thread-safe. If UML comes up: aggregation is the hollow diamond, composition the filled one — but the lifecycle question matters, not the notation.

Interview note: Follow-up: "which one is 'composition over inheritance' about?" All of them, loosely — the phrase means preferring has-a delegation over is-a extension; it is not restricted to strict UML composition.

Q11. Walk me through SOLID — and go deep on Liskov Substitution.

Single Responsibility: one reason to change per class. Open-Closed: extend behavior without editing tested code. Liskov Substitution: a subtype must be usable anywhere its parent is, without callers noticing. Interface Segregation: many small contracts over one fat one. Dependency Inversion: depend on abstractions, wire implementations from outside.

LSP is where interviews dig. The rule beyond signatures: an override may not strengthen preconditions, weaken postconditions, or violate parent invariants. The classic violation is Square extends RectanglesetWidth on a square breaks the independent-dimensions contract, silently corrupting every caller that resizes rectangles. The smell to name: instanceof checks scattered through client code, or an override throwing UnsupportedOperationException — the subtype announcing it cannot honor the contract.

Interview note: Follow-up: "fix Square/Rectangle." Don't force the is-a: make both implement a Shape interface with area(), or make the types immutable so the mutation contract disappears. Recognizing that immutability dissolves the violation is the standout answer.

Q12. Interface vs abstract class — answer it as a design decision.

Abstract class: shared state and partial implementation, single inheritance slot, constructors — a family's common backbone. Interface: pure capability, multiple inheritance of type, default methods but no instance fields — a contract unrelated classes can satisfy. The design question: am I defining what something is (abstract class) or what it can do (interface)?

Add the evolution angle: an interface with default methods can grow without breaking implementers; only an abstract class can enforce an initialization sequence and hold protected helper state. The strongest answer combines them: interface for the public contract, abstract skeleton for the boring parts — how the JDK pairs List with AbstractList. More depth in abstraction in Java.

Interview note: Trap: "an abstract class with zero abstract methods — legal? Why would you?" Legal. It exists purely to block direct instantiation while providing full shared behavior — a base you must extend to use.

Q13. Is Java pass-by-value or pass-by-reference?

Strictly pass-by-value — always. For objects, the value copied is the reference itself. A method can mutate the object its parameter points to (caller sees it), but reassigning the parameter changes only the local copy (caller never sees it).

void update(StringBuilder sb) {
    sb.append(" world");                 // mutation: caller SEES this
    sb = new StringBuilder("bye");       // reassignment: caller NEVER sees this
}
StringBuilder s = new StringBuilder("hello");
update(s);
System.out.println(s);                   // "hello world"

That two-line contrast is the entire answer — commit it to memory as runnable proof. Mutation through a parameter feels like pass-by-reference; the test that settles it is reassignment, which never escapes the method. This is also why a swap method for object references cannot be written in Java — a favorite follow-up.

Interview note: Trap: "so primitives and objects are passed differently?" No — identically, by value. For primitives the value is the data; for objects the value is the reference. One rule, two payloads.

Q14. How has pattern matching changed instanceof and casting?

Since Java 16, instanceof can declare a pattern variable — if (obj instanceof Circle c) tests, casts and binds in one step. Java 21 extended patterns into switch, with sealed hierarchies enabling compiler-checked exhaustiveness.

double area(Shape s) {
    return switch (s) {
        case Circle c    -> Math.PI * c.radius() * c.radius();
        case Square sq   -> sq.side() * sq.side();
        case Triangle t  -> 0.5 * t.base() * t.height();
    };  // sealed Shape: compiler verifies every case is covered
}

The design nuance interviewers want: overriding puts behavior inside the hierarchy; pattern matching over a sealed interface puts it outside — better when operations change often but the type set is closed.

Interview note: Follow-up: "doesn't switching on type violate OOP?" Unsealed, yes — a new subtype silently falls through. Sealed plus exhaustive switch flips it: the compiler forces every new subtype to be handled, which overriding never did for external operations.

How to prepare

Build a single toy domain — payments, bookings, anything you know — and implement every question in it: a hierarchy with an override, a hidden static, a covariant return, a diamond of default methods, a sealed switch. One codebase, fourteen experiments; the traps become things you have watched happen rather than rules you memorized.

Then rehearse the compressed versions: the one-minute pillars (Q1), the dispatch rules (Q3, Q4, Q8), and the pass-by-value proof (Q13) are asked verbatim in screening rounds and should be automatic. Pair this set with the collections questions — equality, immutability and the Stack extends Vector story connect the two — and check the 2-year experience set for how OOP answers get framed around project experience. If you can teach each answer in the Java Full Stack course style — definition, mechanism, consequence — you are ready.

Frequently Asked Questions

Why do interviewers still ask OOP questions to experienced developers?
Because OOP answers expose design maturity fast. A definition-level answer to composition vs inheritance or LSP signals someone who writes code but has not had to maintain it. Experienced candidates are expected to attach consequences and past decisions to every concept.
What is the most common OOP mistake candidates make in interviews?
Confusing overloading with overriding, and claiming Java passes objects by reference. Both come up in nearly every screening round, and both have precise, checkable answers that interviewers use as instant filters.
How deep should freshers go on SOLID principles?
Know all five names and be able to explain Single Responsibility, Open-Closed and Liskov Substitution with one concrete example each. Freshers are not expected to have applied Dependency Inversion at scale, but recognizing a violation in shown code earns real credit.
Is OOP still relevant now that Java has lambdas and records?
Yes — the features complement rather than replace it. Records are a concise syntax for immutable value objects, and lambdas target functional interfaces, which are an OOP construct. Interviews increasingly ask how these features change classic OOP decisions, so knowing both sides helps.
How long should I prepare OOP topics before a Java interview?
For most candidates, three to five focused days: one on pillars and definitions with your own code examples, one on overriding and dispatch rules, one on design questions like composition and SOLID, and one writing and breaking small programs to internalize the traps.

Want to Build Your Career in Java Full Stack with AI?

Join CodeBegun and train with working industry engineers — Explore the Program

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 14 July 2026 LinkedIn
Chat with us