Top 30 OOP Polymorphism and Inheritance Interview Questions & Answers (2026)

What changed in 2026 drives
Mass-recruiter offer letters are flatter for 2026 batch - the 4-5 LPA ASE band has barely budged in three years while inflation eats real wages. Premium tracks (Digital, Pro, Elite, Specialist) are still where the differential lives, and they are entirely test-driven. If you are aiming higher than the default offer, the coding round is not optional pageantry - it is the entire interview.
What I'd actually study for this
- 01Two solid coding-round answers (1 medium-hard DSA each, with edge-case discussion) > five half-baked ones
- 02One real project you can defend end-to-end - file paths, design decisions, and what you would change
- 03One DBMS schema you actually built (not a textbook ER diagram), with at least 3 join-heavy queries written from memory
- 04Three behavioural STAR stories: failure recovered, conflict handled, ownership taken
Where most candidates trip up
The single biggest mistake is treating company-specific guides as primary prep and DSA as secondary. It is the opposite. Mass recruiters use the test as a filter, but premium tracks at every IT services company use coding to allocate offer band. Spend 70% of prep time on DSA + system fundamentals, 20% on company-specific patterns, 10% on HR rehearsal. Reverse that ratio and you collect the default offer.
Editorial commentary by Aditya Sharma · written for PapersAdda · not generated, not aggregated.
Object-oriented programming (OOP) concepts are tested in virtually every placement interview. Candidates report that polymorphism and inheritance questions are among the most common OOP topics at TCS, Infosys, Amazon, Microsoft, and Flipkart. Based on public preparation resources and candidate-reported interview accounts, questions on runtime vs compile-time polymorphism, method resolution, abstract classes vs interfaces, and the diamond problem appear most frequently.
Polymorphism Foundations
Q1. What is polymorphism? Explain its two types with examples. (Easy)
Polymorphism (Greek: "many forms") means the same interface can refer to objects of different types, and behavior adapts based on the actual object type.
Two types:
Compile-time polymorphism (Static dispatch, Method Overloading): Resolved at compile time by the compiler based on the method signature (parameter types and count).
class Calculator {
public int add(int a, int b) { return a + b; }
public double add(double a, double b) { return a + b; }
public int add(int a, int b, int c) { return a + b + c; }
}
Calculator c = new Calculator();
c.add(1, 2); // calls int version
c.add(1.5, 2.5); // calls double version
Runtime polymorphism (Dynamic dispatch, Method Overriding): Resolved at runtime based on the actual object type, not the reference type.
class Animal {
public void sound() { System.out.println("Generic animal sound"); }
}
class Dog extends Animal {
@Override
public void sound() { System.out.println("Woof"); }
}
class Cat extends Animal {
@Override
public void sound() { System.out.println("Meow"); }
}
Animal a = new Dog(); // reference type: Animal, actual type: Dog
a.sound(); // prints "Woof" (runtime dispatch)
Q2. How does dynamic dispatch (virtual method dispatch) work internally? (Medium)
In languages like Java and C++, every class with virtual methods has a vtable (virtual method table): an array of function pointers, one per virtual method.
Structure:
Dog vtable:
[0] -> Dog::sound()
[1] -> Animal::toString()
...
Cat vtable:
[0] -> Cat::sound()
[1] -> Animal::toString()
...
Every object instance has a hidden pointer (vptr) to its class's vtable. When you call a.sound() where a is an Animal reference:
- JVM/runtime dereferences the vptr to find the vtable
- Looks up
sound()at slot 0 in the vtable - Calls the function pointer found there
For a Dog object: vtable slot 0 points to Dog::sound().
For a Cat object: vtable slot 0 points to Cat::sound().
Performance: vtable dispatch adds one indirection versus a direct function call. This is why virtual dispatch has slight overhead compared to static calls -- typically 1-3 nanoseconds on modern hardware, negligible for most applications.
Java specifics: Java methods are virtual by default (unlike C++ where you must declare virtual). Only final, static, and private methods are not virtual in Java and can be resolved at compile time (devirtualization).
Q3. What is method overriding? What rules must be followed? (Easy)
Method overriding occurs when a subclass provides a new implementation for a method already defined in the parent class with the same signature.
Rules for correct overriding (Java):
- Same method name and parameter list (exact signature match)
- Return type: same type or a covariant type (subtype of original return type)
- Access modifier: cannot be more restrictive than parent; can be less restrictive
- parent:
protected-> child can beprotectedorpublic, NOTprivate
- parent:
- Checked exceptions: cannot throw new or broader checked exceptions than the parent
- Cannot override:
finalmethods,staticmethods (those are hidden, not overridden),privatemethods - @Override annotation: not required but recommended; causes compile error if not actually overriding
Covariant return type example:
class Animal {
public Animal create() { return new Animal(); }
}
class Dog extends Animal {
@Override
public Dog create() { return new Dog(); } // Dog is subtype of Animal; covariant, valid
}
Q4. What is the difference between this and super in Java? (Easy)
this: Refers to the current object instance. Used to:
- Distinguish instance variables from local variables when they share names
- Call another constructor in the same class (
this(args)- constructor chaining) - Pass the current object as an argument
super: Refers to the parent class. Used to:
- Access parent class members hidden by subclass members
- Call parent class constructor (
super(args)- must be first statement in constructor) - Explicitly call parent class version of an overridden method
class Animal {
protected String name;
public Animal(String name) { this.name = name; }
public void describe() { System.out.println("Animal: " + name); }
}
class Dog extends Animal {
private String breed;
public Dog(String name, String breed) {
super(name); // call Animal constructor
this.breed = breed; // this: distinguish field from parameter
}
@Override
public void describe() {
super.describe(); // call Animal.describe() first
System.out.println("Breed: " + breed);
}
}
super() must be first line in constructor. If not explicitly called, Java inserts super() (no-arg parent constructor) automatically. If parent has no no-arg constructor, you must call super(args) explicitly.
Q5. Can you override a static method? What is method hiding? (Medium)
No, you cannot override a static method. Static methods belong to the class, not instances. They are resolved at compile time based on the reference type, not at runtime based on the object type.
If a subclass defines a static method with the same signature as a parent's static method, this is called method hiding, not overriding.
class Parent {
public static void greet() { System.out.println("Hello from Parent"); }
public void instanceGreet() { System.out.println("Instance: Parent"); }
}
class Child extends Parent {
public static void greet() { System.out.println("Hello from Child"); }
@Override
public void instanceGreet() { System.out.println("Instance: Child"); }
}
Parent p = new Child();
p.greet(); // "Hello from Parent" (static: compile-time, reference type wins)
p.instanceGreet(); // "Instance: Child" (instance: runtime, actual type wins)
Key distinction:
- Static method: resolved at compile time using reference type -> method hiding
- Instance method: resolved at runtime using actual object type -> method overriding
@Override on static method: Compiler error. @Override requires that the method actually overrides a superclass instance method or implements an interface method.
Inheritance Deep Dive
Q6. What is the difference between IS-A and HAS-A relationships? When do you use each? (Easy)
IS-A relationship (Inheritance):
Models a hierarchical classification. "A Dog IS-A Animal." Implemented via extends (class) or implements (interface).
Use inheritance when:
- The subclass truly is a specialized version of the parent
- The subclass can be substituted for the parent everywhere (LSP)
- You want to reuse and extend parent behavior
HAS-A relationship (Composition): Models ownership or containment. "A Car HAS-A Engine." Implemented via instance variables.
Use composition when:
- The relationship is containment, not classification
- You want to change behavior at runtime (swap implementations)
- You need to reuse code from multiple "parents" (Java has single inheritance)
- The component is shared across many classes
Classic confusion: Stack and Vector (Java):
Java's Stack extends Vector is a famous bad design. A Stack IS-NOT-A Vector -- a Stack should only expose push/pop/peek, but inheriting Vector exposes all Vector methods (add at index, remove, etc.), violating the Stack abstraction.
Fix: Stack should have a Vector or List (composition, HAS-A), not extend one.
Rule of thumb: Favor composition over inheritance for code reuse. Use inheritance only for genuine type hierarchies.
Q7. What is the diamond problem? How does Java avoid it and how does C++ solve it? (Medium)
The diamond problem occurs with multiple class inheritance:
A
/ \
B C
\ /
D
If A defines void display(), B overrides it, C overrides it, and D inherits from both B and C, which display() does D get?
Java's approach -- No multiple class inheritance: Java avoids the diamond problem by prohibiting multiple class inheritance. A class can extend only one class. However, Java allows implementing multiple interfaces. With default methods in interfaces (Java 8+), diamond can appear:
interface A { default void greet() { System.out.println("A"); } }
interface B extends A { default void greet() { System.out.println("B"); } }
interface C extends A { default void greet() { System.out.println("C"); } }
interface D extends B, C {} // Compile error: diamond conflict
Java requires class D to explicitly override greet() when diamond conflict exists in interfaces.
C++ solution -- Virtual inheritance:
class A { public: void display() { cout << "A"; } };
class B : virtual public A {}; // virtual: share A's subobject
class C : virtual public A {};
class D : public B, public C {};
D d;
d.display(); // Unambiguous: only one A subobject (virtual inheritance shares it)
Without virtual, C++ creates two separate A subobjects in D, causing ambiguity and calling d.display() fails to compile.
Python's resolution -- MRO (Method Resolution Order):
Python uses C3 linearization. D.greet() calls the first definition found in left-to-right depth-first MRO, with each class appearing only once.
Q8. What is the Fragile Base Class problem? (Hard)
The Fragile Base Class problem occurs when changes to a base class break subclasses, even when the changes seem safe in isolation.
Example:
class Counter {
private int count = 0;
public void increment() { count++; }
public void incrementBy(int n) {
for (int i = 0; i < n; i++) {
increment(); // calls increment() which subclass may override
}
}
}
class CountingCounter extends Counter {
private int totalCalls = 0;
@Override
public void increment() {
totalCalls++;
super.increment();
}
}
Now suppose a maintainer optimizes Counter.incrementBy():
// "Safe" optimization: replace loop with direct arithmetic
public void incrementBy(int n) {
count += n; // No longer calls increment()
}
This breaks CountingCounter: totalCalls no longer tracks calls from incrementBy(). The base class change broke the subclass even though the observable behavior of Counter itself did not change.
Root causes:
- Subclass depends on implementation details of base class, not just its contract
- Base class cannot know which methods subclasses are overriding
Mitigation:
- Prefer composition over inheritance for code reuse
- Document which methods are safe to override and what invariants they must maintain
- Use
finalon methods not designed for override - In Java: design for inheritance or prohibit it (Effective Java, Item 19)
Q9. What does final mean in Java for classes, methods, and variables? (Easy)
final restricts modification or extension:
Final class:
Cannot be extended (subclassed). Example: java.lang.String, java.lang.Integer.
final class ImmutablePoint {
final double x, y;
ImmutablePoint(double x, double y) { this.x = x; this.y = y; }
}
// class ExtendedPoint extends ImmutablePoint {} // Compile error
Final method: Cannot be overridden by subclasses. Useful to prevent subclasses from breaking invariants.
class Account {
final void validateBalance() { /* critical logic */ }
}
Final variable: Must be initialized exactly once; cannot be reassigned. Local final variable: assigned once in its scope. Instance final variable: assigned in constructor or field initializer.
final int MAX = 100; // constant
final List<String> names = new ArrayList<>();
names.add("Alice"); // OK: contents can change
// names = new ArrayList<>(); // Error: reassignment not allowed
Why use final?
- Performance: JIT can inline final methods (devirtualize)
- Design clarity: communicates intent (this should not be overridden/reassigned)
- Security: prevents malicious subclassing (Secure coding practice for sensitive classes)
- Immutability: final fields + no setters = immutable object
Q10. Explain constructor inheritance. Is a constructor inherited in Java? (Medium)
Constructors are NOT inherited in Java. Each class must define its own constructors. However, Java requires that every constructor in a subclass explicitly or implicitly calls a constructor in the parent class using super().
Implicit super() call:
If you do not write super() explicitly, Java inserts super() (the no-arg constructor of the parent) as the first statement.
class Animal {
String name;
Animal() { System.out.println("Animal default constructor"); }
Animal(String name) { this.name = name; }
}
class Dog extends Animal {
Dog() {
// Java inserts: super(); here automatically
System.out.println("Dog constructor");
}
}
new Dog(); // Output: "Animal default constructor" then "Dog constructor"
If parent has no no-arg constructor:
class Vehicle {
int speed;
Vehicle(int speed) { this.speed = speed; } // Only parameterized constructor
}
class Car extends Vehicle {
Car() {
// super(); would fail -- no Vehicle() no-arg constructor
super(100); // Must explicitly call the parameterized constructor
}
}
Constructor chaining order: Constructors execute top-down: grandparent -> parent -> child. Object initialization executes before the child's constructor body.
Abstract Classes and Interfaces
Q11. What is the difference between abstract classes and interfaces in Java? (Easy)
| Feature | Abstract Class | Interface |
|---|---|---|
| Instantiation | Cannot instantiate | Cannot instantiate |
| Inheritance | Single (extends) | Multiple (implements) |
| State (fields) | Instance variables allowed | Only constants (public static final) |
| Constructor | Can have constructors | No constructors |
| Methods | Abstract + concrete + static | Abstract + default + static (Java 8+), private (Java 9+) |
| Access modifiers | Any | Methods public by default |
When to use abstract class:
- Shared state among related classes (e.g.,
AbstractListhas sharedmodCount) - Significant shared code that all subclasses need
- Template Method pattern: define algorithm skeleton, defer steps to subclasses
When to use interface:
- Define a capability or contract:
Comparable,Serializable,Runnable - Unrelated classes need common behavior (a
Duckand aAirplanecan both beFlyable) - You need multiple inheritance of type
Java 8+ nuance: Default methods in interfaces allow shared implementation. But they cannot have state, so when shared state is needed, abstract class is still the right choice.
Q12. What is the Template Method pattern? How does it use abstract classes? (Medium)
The Template Method pattern defines the skeleton of an algorithm in a base class and defers some steps to subclasses. The base class defines the invariant "template" method as final; subclasses override the variable steps.
abstract class DataExporter {
// Template method: final to prevent overriding the algorithm structure
public final void export() {
connect();
List<String[]> data = extractData(); // subclass implements
String formatted = format(data); // subclass implements
write(formatted);
disconnect();
}
protected abstract List<String[]> extractData();
protected abstract String format(List<String[]> data);
private void connect() { System.out.println("Connecting to source"); }
private void write(String content) { System.out.println("Writing: " + content); }
private void disconnect() { System.out.println("Disconnecting"); }
}
class CSVExporter extends DataExporter {
protected List<String[]> extractData() {
return List.of(new String[]{"Alice", "30"}, new String[]{"Bob", "25"});
}
protected String format(List<String[]> data) {
StringBuilder sb = new StringBuilder();
for (String[] row : data) sb.append(String.join(",", row)).append("\n");
return sb.toString();
}
}
class JSONExporter extends DataExporter {
protected List<String[]> extractData() {
return List.of(new String[]{"Alice", "30"});
}
protected String format(List<String[]> data) {
return "[{\"name\":\"" + data.get(0)[0] + "\",\"age\":\"" + data.get(0)[1] + "\"}]";
}
}
What Template Method provides:
- OCP: new export formats add new classes without modifying
export()algorithm - SRP: each exporter is responsible for its own format
- Code reuse: connect/disconnect logic in one place
Q13. What is a marker interface? Give examples. (Easy)
A marker interface (also called tag interface) is an empty interface with no methods. It marks a class as having a certain property, which can then be checked with instanceof.
Java built-in marker interfaces:
// These have no methods:
java.io.Serializable // marks class as serializable
java.lang.Cloneable // marks class as safely cloneable
java.util.RandomAccess // marks List as having O(1) random access
java.rmi.Remote // marks object as accessible remotely
Usage:
if (obj instanceof Serializable) {
serialize(obj);
}
Modern alternative -- annotations:
@Serializable // custom annotation
class MyClass { ... }
Annotations are more flexible (can carry metadata, be processed at compile time by annotation processors). Marker interfaces are checked at runtime with instanceof; annotations are checked via reflection with isAnnotationPresent().
When marker interfaces are still better: Marker interfaces work with generic type bounds, which annotations cannot:
<T extends Serializable> void serialize(T obj) { ... }
// Compile-time enforcement: T must be Serializable
Q14. What is an inner class in Java? How does it differ from a static nested class? (Medium)
Inner class (non-static nested class):
- Defined inside another class without
statickeyword - Has access to all members (including private) of the enclosing class
- Each inner class instance is tied to an instance of the enclosing class
- Cannot have static members (except constants)
class Outer {
private int x = 10;
class Inner {
void display() {
System.out.println(x); // Can access outer x directly
}
}
}
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // Requires outer instance
Static nested class:
- Defined inside another class WITH
statickeyword - Does not have access to instance members of the enclosing class (only static members)
- Can be instantiated without an instance of the enclosing class
- Can have static members
class Outer {
private static int staticX = 10;
private int instanceX = 20;
static class StaticNested {
void display() {
System.out.println(staticX); // OK: static member
// System.out.println(instanceX); // Error: cannot access instance member
}
}
}
Outer.StaticNested nested = new Outer.StaticNested(); // No outer instance needed
When to use each:
- Inner class: when the class logically belongs to and needs access to the outer class (iterators, event handlers)
- Static nested: when logically grouped with the outer class but independent (Builder pattern inner class, helper class)
Polymorphism Scenarios and Tricky Questions
Q15. Predict the output of this code. (Medium)
class A {
public void greet() { System.out.println("Hello from A"); }
public A create() { return new A(); }
}
class B extends A {
@Override
public void greet() { System.out.println("Hello from B"); }
@Override
public B create() { return new B(); }
}
A obj = new B();
obj.greet();
obj.create().greet();
Line 1: obj.greet()
objis declared asAbut refers to aBobjectgreet()is overridden inB- Runtime dispatch: actual type
B-> callsB.greet() - Output:
Hello from B
Line 2: obj.create().greet()
obj.create(): runtime dispatch, actual type isB, callsB.create(), returns aBobject- Return type of
obj.create()in compile-time isA(becauseobjis declared asA) .greet()on the returned object: actual type isB(returned byB.create())- Runtime dispatch: calls
B.greet() - Output:
Hello from B
Full output:
Hello from B
Hello from B
Q16. What happens when you call an overridden method from a constructor? (Hard)
This is a dangerous pattern. When an overridden method is called from a parent constructor, the child class's version is called -- but the child class has not been initialized yet.
class Base {
int x;
Base() {
System.out.println("Base constructor called");
init(); // calls overridden method during construction
}
public void init() {
x = 10;
System.out.println("Base.init, x=" + x);
}
}
class Derived extends Base {
int y = 20; // initialized AFTER Base constructor completes
@Override
public void init() {
x = 100;
System.out.println("Derived.init, y=" + y); // y is still 0 here!
}
}
new Derived();
Output:
Base constructor called
Derived.init, y=0 // y is 0 because Derived field initializer hasn't run yet
Execution order:
new Derived()calledBase()constructor invoked first (Java insertssuper()in Derived constructor)- Inside
Base():init()is virtual -> callsDerived.init() - At this point,
Derived.yis still 0 (field initialization runs after super constructor) - After
Base()completes:Derived.y = 20is set - Derived constructor body runs
Rule: Never call overrideable (non-final, non-private) methods from constructors. The object is in a partially initialized state when virtual methods are called from constructors.
Q17. What is duck typing? How does Python implement polymorphism differently from Java? (Medium)
Duck typing (Python, dynamic languages): "If it walks like a duck and quacks like a duck, it's a duck." Polymorphism based on the presence of methods/attributes, not on type hierarchy. No need to inherit from a common base class.
class Dog:
def sound(self):
return "Woof"
class Cat:
def sound(self):
return "Meow"
class Duck:
def sound(self):
return "Quack"
def make_sound(animal): # No type annotation needed
print(animal.sound()) # Works for any object with a sound() method
make_sound(Dog()) # Woof
make_sound(Cat()) # Meow
make_sound(Duck()) # Quack
Dog, Cat, and Duck share no common parent class. Polymorphism works purely because they all have sound().
Java (nominal typing): Polymorphism requires explicit type relationships. Either:
- Inherit from a common base class
- Implement a common interface
interface Sounding { String sound(); }
class Dog implements Sounding { public String sound() { return "Woof"; } }
void makeSound(Sounding s) { System.out.println(s.sound()); }
Comparison:
| Feature | Python (duck typing) | Java (nominal typing) |
|---|---|---|
| Type check | Runtime (AttributeError if missing) | Compile time (type error) |
| Requires common parent | No | Yes (class or interface) |
| Flexibility | High | Lower |
| Error detection | Runtime | Compile time |
| IDE support | Limited | Full |
Python ABC (Abstract Base Classes): Python's abc module adds nominal typing optionally, combining duck typing flexibility with explicit contracts.
Q18. What is method chaining? How does it use polymorphism? (Medium)
Method chaining (fluent interface) is a technique where each method returns this (or a new instance), allowing multiple method calls to be chained on one line.
StringBuilder sb = new StringBuilder()
.append("Hello")
.append(", ")
.append("World")
.append("!");
Each append() returns the same StringBuilder instance, enabling chaining.
Polymorphism in builders:
abstract class QueryBuilder<T extends QueryBuilder<T>> {
protected String table;
protected String condition;
public T from(String table) {
this.table = table;
return self();
}
public T where(String condition) {
this.condition = condition;
return self();
}
protected abstract T self(); // returns the actual subtype
public abstract String build();
}
class SelectBuilder extends QueryBuilder<SelectBuilder> {
private String columns = "*";
public SelectBuilder select(String cols) { this.columns = cols; return this; }
protected SelectBuilder self() { return this; }
public String build() {
return "SELECT " + columns + " FROM " + table +
(condition != null ? " WHERE " + condition : "");
}
}
String sql = new SelectBuilder()
.from("users")
.where("age > 18")
.select("name, email")
.build();
// SELECT name, email FROM users WHERE age > 18
The T extends QueryBuilder<T> pattern (recursive generic bound) ensures subclass methods return the subtype, maintaining the correct return type in chains even through inherited methods.
Q19. Explain the concept of upcasting and downcasting. When does ClassCastException occur? (Medium)
Upcasting: Converting a subclass reference to a parent class reference. Always safe (implicit, no cast needed).
Dog dog = new Dog();
Animal animal = dog; // Upcasting: implicit, always safe
Downcasting: Converting a parent class reference to a subclass reference. May be unsafe (requires explicit cast).
Animal animal = new Dog();
Dog dog = (Dog) animal; // Downcasting: explicit cast, must verify
dog.fetch(); // OK: actual object is Dog
ClassCastException: Occurs at runtime when you try to downcast to a type the object is not an instance of:
Animal animal = new Cat(); // actual type: Cat
Dog dog = (Dog) animal; // ClassCastException! Cat is not a Dog
Safe downcasting with instanceof:
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.fetch();
}
Java 16+ pattern matching for instanceof:
if (animal instanceof Dog dog) { // declares and casts in one step
dog.fetch();
}
When is downcasting needed? When you retrieve objects from a collection of base type and need to access subtype-specific methods. However, frequent downcasting is a design smell (often violates LSP or indicates missing interface methods).
Q20. What is the difference between equals() and == in Java? How do polymorphism and inheritance affect it? (Medium)
== compares references (memory addresses). For objects, true only when both variables point to the same object.
equals() is a method on Object that compares logical equality. Default implementation in Object uses ==, but it is meant to be overridden.
String a = new String("hello");
String b = new String("hello");
a == b // false: different objects in memory
a.equals(b) // true: same character sequence
Polymorphism and equals:
equals() is overridden in String, Integer, List, etc. When called on a Object reference pointing to a String, runtime dispatch calls String.equals().
Inheritance contract for equals (from Object documentation):
- Reflexive:
x.equals(x)must be true - Symmetric:
x.equals(y)iffy.equals(x) - Transitive: if
x.equals(y)andy.equals(z), thenx.equals(z) - Consistent: multiple calls return same result if no state change
- Null-safe:
x.equals(null)must return false
equals and inheritance problem:
class Point {
int x, y;
@Override
public boolean equals(Object o) {
if (!(o instanceof Point)) return false;
Point p = (Point) o;
return x == p.x && y == p.y;
}
}
class ColorPoint extends Point {
String color;
@Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint)) return false;
ColorPoint cp = (ColorPoint) o;
return super.equals(cp) && color.equals(cp.color);
}
}
Symmetry violation: point.equals(colorPoint) could be true (Point only checks x,y), but colorPoint.equals(point) is false (ColorPoint also checks color). Cannot fix without sacrificing substitutability or symmetry. This is why mixing equals() with inheritance is tricky -- prefer composition over inheritance when equality semantics need to differ.
Always override hashCode() when overriding equals(): equal objects must have equal hash codes (contract required by HashMap, HashSet).
Q21. What is an abstract method and when must it be overridden? (Easy)
An abstract method has no body (no implementation) in the declaring class. Concrete subclasses must provide an implementation.
abstract class Shape {
abstract double area(); // No body
abstract double perimeter(); // No body
public void printInfo() {
System.out.println("Area: " + area() + ", Perimeter: " + perimeter());
}
}
class Circle extends Shape {
double radius;
Circle(double r) { radius = r; }
@Override
double area() { return Math.PI * radius * radius; }
@Override
double perimeter() { return 2 * Math.PI * radius; }
}
Rules:
- A class with any abstract method must be declared
abstract - An abstract class cannot be instantiated directly
- A concrete subclass must override all abstract methods from its parent; if it does not, the subclass must also be declared
abstract - Abstract methods cannot be
private(private = not visible to subclasses) orfinal(final = cannot be overridden)
Design purpose: Abstract methods enforce a contract. The parent class guarantees that all subclasses will have area() and perimeter(), allowing printInfo() to call them via polymorphism.
Q22. What is the difference between extends and implements? When can you use each? (Easy)
| Keyword | Purpose | Multiplicity |
|---|---|---|
extends | Inherit from a class or extend an interface | One (class extends class), multiple (interface extends interface) |
implements | Implement an interface | Multiple (class can implement many interfaces) |
Class hierarchy rules:
- Class
extendsclass: single inheritance, inherit state + behavior - Class
implementsinterface: multiple allowed, provides type contract - Interface
extendsinterface: multiple allowed, extend the contract - Interface cannot
implementsanything
class Animal {}
interface Flyable {}
interface Swimmable {}
class Duck extends Animal implements Flyable, Swimmable {
// Duck IS-A Animal, can fly, can swim
}
interface MotorizedVehicle extends Flyable, Swimmable {
// Interface can extend multiple interfaces
}
Practical decision:
Choose extends (abstract class) when: shared state, shared constructor logic, IS-A relationship with significant code sharing.
Choose implements (interface) when: defining a capability, multiple "IS-A" relationships needed, unrelated classes need same contract.
Q23. What is a functional interface in Java? How does it relate to polymorphism? (Medium)
A functional interface is an interface with exactly one abstract method. It can have multiple default or static methods, but only one abstract method.
@FunctionalInterface
interface Transformer {
String transform(String input); // single abstract method
// Can have default methods:
default String transformAndLog(String input) {
String result = transform(input);
System.out.println("Transformed: " + result);
return result;
}
}
Lambda expressions and functional interfaces: Java 8 lambdas are instances of functional interfaces, making them a form of anonymous polymorphism:
Transformer upper = s -> s.toUpperCase(); // anonymous implementation
Transformer trim = String::trim; // method reference
upper.transform("hello"); // "HELLO"
trim.transform(" hi "); // "hi"
Polymorphism aspect: Transformer is a reference type. Multiple implementations (lambdas, method references, anonymous classes) can be stored in Transformer variables and called polymorphically.
Built-in functional interfaces (java.util.function):
| Interface | Signature | Common use |
|---|---|---|
Function<T,R> | R apply(T t) | Transformation |
Predicate<T> | boolean test(T t) | Filtering |
Consumer<T> | void accept(T t) | Side effects |
Supplier<T> | T get() | Lazy creation |
BiFunction<T,U,R> | R apply(T t, U u) | Two-argument function |
Q24. Explain covariance and contravariance in generics. (Hard)
Covariance: List<Dog> is a subtype of List<Animal> -- if Dog is a subtype of Animal.
Java arrays are covariant by default (this was a mistake, causing ArrayStoreException).
Java generics are invariant by default.
Invariance of Java generics:
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = dogs; // Compile error! Not allowed.
// If it were allowed, you could add a Cat to dogs via the animals reference.
Wildcard covariance (upper-bounded):
List<? extends Animal> animals = new ArrayList<Dog>(); // OK
Animal a = animals.get(0); // OK: guaranteed to be Animal or subtype
// animals.add(new Cat()); // Error: cannot add to upper-bounded wildcard
? extends Animal is the "producer" -- you can read from it (covariant).
Wildcard contravariance (lower-bounded):
List<? super Dog> dogOrHigher = new ArrayList<Animal>(); // OK
dogOrHigher.add(new Dog()); // OK: adding Dog to List<Animal>
dogOrHigher.add(new Animal()); // Error: might be List<Dog>, can't add plain Animal
// Dog d = dogOrHigher.get(0); // Error: reading returns Object
? super Dog is the "consumer" -- you can write to it (contravariant).
PECS (Producer Extends, Consumer Super) - Joshua Bloch:
- If a collection produces (provides) elements of type T:
<? extends T> - If a collection consumes (accepts) elements of type T:
<? super T> - If both:
<T>(invariant)
Q25. What is the difference between composition and aggregation in OOP? (Medium)
Both are HAS-A relationships, but they differ in lifecycle:
Composition (Strong HAS-A): The contained object cannot exist independently of the container. If the container is destroyed, the contained object is destroyed too. The child is entirely owned by the parent.
Example: A House HAS-A Room. If the house is demolished, the rooms cease to exist as rooms. Rooms don't exist independently outside a house.
class Room {
String type;
Room(String type) { this.type = type; }
}
class House {
private final List<Room> rooms = new ArrayList<>();
House() {
rooms.add(new Room("Bedroom")); // Room created by House
rooms.add(new Room("Kitchen")); // Room is owned by House
}
// When House is garbage collected, Rooms have no other references -> also collected
}
Aggregation (Weak HAS-A): The contained object can exist independently of the container. If the container is destroyed, the contained object continues to exist.
Example: A Department HAS-A Professor. If the department is closed, the professor continues to exist (moves to another department).
class Professor {
String name;
Professor(String name) { this.name = name; }
}
class Department {
private List<Professor> professors = new ArrayList<>();
void addProfessor(Professor p) {
professors.add(p); // Professor exists independently; just associated
}
}
Professor prof = new Professor("Dr. Mehta");
Department dept = new Department();
dept.addProfessor(prof);
dept = null; // Department destroyed, but prof still exists
UML notation:
- Composition: filled diamond on the whole side
- Aggregation: hollow diamond on the whole side
Advanced OOP Concepts
Q26. What is the prototype pattern and how does it use cloning? (Medium)
The Prototype pattern creates new objects by cloning an existing object (the prototype) instead of creating from scratch. Useful when object creation is expensive.
Java: Cloneable interface and clone():
class ExpensiveObject implements Cloneable {
private String data;
private List<String> cachedResults;
public ExpensiveObject(String data) {
this.data = data;
// Simulate expensive computation
this.cachedResults = computeExpensiveResults(data);
}
private List<String> computeExpensiveResults(String data) {
// Takes 2 seconds...
return List.of(data.toUpperCase(), data.toLowerCase());
}
@Override
public ExpensiveObject clone() {
try {
ExpensiveObject cloned = (ExpensiveObject) super.clone(); // shallow copy
cloned.cachedResults = new ArrayList<>(this.cachedResults); // deep copy list
return cloned;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
ExpensiveObject original = new ExpensiveObject("hello"); // expensive
ExpensiveObject copy = original.clone(); // cheap: no recomputation
Shallow vs deep copy:
- Shallow copy: copies the object but not the objects it references (they share references)
- Deep copy: recursively copies all referenced objects
In the example, super.clone() performs a shallow copy. We manually deep-copy the cachedResults list to ensure independence.
Prototype registry pattern: Store prototypes in a registry. Clients clone from the registry instead of constructing:
Map<String, ExpensiveObject> registry = new HashMap<>();
registry.put("hello-prototype", new ExpensiveObject("hello"));
ExpensiveObject copy = registry.get("hello-prototype").clone();
Q27. What are sealed classes in Java 17+? How do they relate to inheritance? (Hard)
Sealed classes (Java 17, JEP 409) restrict which classes can extend or implement them. The parent class explicitly lists all permitted subclasses.
sealed class Shape permits Circle, Rectangle, Triangle { }
final class Circle extends Shape {
double radius;
}
final class Rectangle extends Shape {
double width, height;
}
non-sealed class Triangle extends Shape {
// non-sealed: can be extended further
}
Rules:
- All permitted subclasses must be in the same package (or module)
- Each permitted subclass must use exactly one of:
final,sealed, ornon-sealed final: cannot be extended furthersealed: can be extended but must also declare permitsnon-sealed: reopens hierarchy for extension
Benefit -- exhaustive pattern matching (Java 21 switch):
double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius * c.radius;
case Rectangle r -> r.width * r.height;
case Triangle t -> calculateTriangleArea(t);
// No default needed: compiler knows all permitted subclasses
};
}
Without sealed classes, the switch would need a default or the compiler cannot guarantee all cases are covered.
Use case: Domain modeling where a closed set of variants is intentional. Similar to enum but for class hierarchies with state.
Q28. What is the Visitor pattern and how does it enable double dispatch? (Hard)
The Visitor pattern adds new operations to a class hierarchy without modifying the classes. It separates the operation from the data structure.
The double dispatch problem: Java supports single dispatch: which method to call is determined by the runtime type of the receiver object. But if you want behavior to depend on both the type of the visitor AND the type of the element, you need double dispatch.
// Hierarchy of shapes
interface Shape {
void accept(ShapeVisitor visitor);
}
class Circle implements Shape {
double radius;
public void accept(ShapeVisitor visitor) {
visitor.visit(this); // dispatch 1: calls the correct visit() overload for Circle
}
}
class Rectangle implements Shape {
double width, height;
public void accept(ShapeVisitor visitor) {
visitor.visit(this); // dispatch 1: calls the correct visit() overload for Rectangle
}
}
// Visitor interface
interface ShapeVisitor {
void visit(Circle circle);
void visit(Rectangle rectangle);
}
// Concrete visitor: area calculation
class AreaCalculator implements ShapeVisitor {
double totalArea = 0;
public void visit(Circle c) { // dispatch 2: correct visit() based on concrete type
totalArea += Math.PI * c.radius * c.radius;
}
public void visit(Rectangle r) {
totalArea += r.width * r.height;
}
}
// Usage
List<Shape> shapes = List.of(new Circle(), new Rectangle());
AreaCalculator calc = new AreaCalculator();
for (Shape s : shapes) {
s.accept(calc); // Double dispatch: first by Shape type, then by Visitor type
}
Why "double dispatch":
s.accept(calc): dispatched based on runtime type ofs(Circle or Rectangle) -> calls correctaccept()visitor.visit(this): dispatched based on the concrete type ofthisinsideaccept()-> calls correctvisit()overload in the visitor
The combination determines behavior based on both the shape AND the visitor type.
OCP benefit: Adding a new visitor (e.g., PerimeterCalculator) requires no modification to Shape, Circle, or Rectangle.
Q29. What is a mixin and how is it achieved in Java and Python? (Medium)
A mixin is a class that provides methods for use by other classes without being intended as a standalone class. It adds behavior without IS-A semantics.
Java: Simulate mixins with default methods in interfaces:
interface Printable {
default void print() {
System.out.println(toString());
}
}
interface Loggable {
default void log(String msg) {
System.out.println("[LOG] " + msg);
}
}
class Invoice implements Printable, Loggable {
// Gets print() and log() for free via default methods
// Mixins without inheritance
}
Invoice inv = new Invoice();
inv.print(); // from Printable mixin
inv.log("Created"); // from Loggable mixin
Limitations: Java interface default methods cannot have state (no instance variables). If mixin behavior requires state, you must use abstract class (single inheritance limit applies).
Python: Multiple inheritance as mixins:
class PrintMixin:
def print(self):
print(str(self))
class LogMixin:
def log(self, msg):
print(f"[LOG] {msg}")
class Invoice(PrintMixin, LogMixin):
def __init__(self, amount):
self.amount = amount
def __str__(self):
return f"Invoice: {self.amount}"
inv = Invoice(1000)
inv.print() # Invoice: 1000
inv.log("Done") # [LOG] Done
Python mixins can have state (instance variables) because it supports true multiple inheritance. MRO ensures correct method resolution when multiple mixins provide the same method.
Q30. Design an inheritance hierarchy for a vehicle rental system. Apply OOP principles. (Hard)
Requirements: Represent cars, bikes, and trucks. All are rentable. Cars and trucks can carry cargo (with weight limits). All vehicles have make, model, and year. Rentals have a daily rate.
Design:
// Abstraction for rentable items
interface Rentable {
double dailyRate();
String rentalDescription();
}
// Base class: shared state and behavior
abstract class Vehicle implements Rentable {
protected final String make;
protected final String model;
protected final int year;
protected final double baseDailyRate;
protected Vehicle(String make, String model, int year, double baseDailyRate) {
this.make = make;
this.model = model;
this.year = year;
this.baseDailyRate = baseDailyRate;
}
@Override
public double dailyRate() { return baseDailyRate; }
@Override
public String rentalDescription() {
return year + " " + make + " " + model;
}
// Template method: calculate total rental cost
public final double rentalCost(int days) {
return dailyRate() * days + additionalFees();
}
protected double additionalFees() { return 0; } // hook method
}
// Cargo-capable vehicles: composition of cargo capacity
interface CargoBearing {
double maxCargoKg();
boolean canCarry(double weightKg);
}
class Car extends Vehicle {
private final int numSeats;
public Car(String make, String model, int year, int numSeats) {
super(make, model, year, 50.0); // 50/day base
this.numSeats = numSeats;
}
@Override
public String rentalDescription() {
return super.rentalDescription() + " (" + numSeats + " seats)";
}
}
class Bike extends Vehicle {
private final String bikeType; // "SCOOTER", "CRUISER", "SPORT"
public Bike(String make, String model, int year, String bikeType) {
super(make, model, year, 25.0);
this.bikeType = bikeType;
}
}
class Truck extends Vehicle implements CargoBearing {
private final double cargoCapacityKg;
public Truck(String make, String model, int year, double cargoCapacityKg) {
super(make, model, year, 120.0);
this.cargoCapacityKg = cargoCapacityKg;
}
@Override
public double maxCargoKg() { return cargoCapacityKg; }
@Override
public boolean canCarry(double weightKg) { return weightKg <= cargoCapacityKg; }
@Override
protected double additionalFees() { return 20.0; } // truck cleaning fee
@Override
public double dailyRate() { return baseDailyRate + (cargoCapacityKg / 1000) * 10; }
}
// Usage: polymorphic processing
List<Rentable> fleet = List.of(
new Car("Toyota", "Camry", 2024, 5),
new Bike("Honda", "Activa", 2024, "SCOOTER"),
new Truck("Tata", "Prima", 2024, 5000)
);
for (Rentable r : fleet) {
System.out.printf("%s -> Rate: %.2f%n", r.rentalDescription(), r.dailyRate());
}
Design decisions justified:
Vehicleis abstract (cannot rent a generic "Vehicle")Rentableis an interface (other rentable things, like trailers, may not be Vehicles)CargoBearingis a separate interface (ISP: not all vehicles bear cargo)rentalCost()isfinaltemplate method in Vehicle (OCP: cannot accidentally override cost calculation)additionalFees()is a hook: subclasses override to add fees (Truck cleaning fee)dailyRate()inTruckis overridden to make rate depend on capacity (polymorphism)- All state is in the right level of the hierarchy (LSP: every Vehicle has make/model/year)
Frequently Asked Questions
Q: Is Java purely object-oriented? No. Java has primitive types (int, double, boolean, etc.) that are not objects. Primitives are not derived from Object and cannot be used in collections without autoboxing. Languages like Smalltalk and Ruby are considered purely OO (everything is an object).
Q: Can you achieve multiple inheritance behavior in Java without using interfaces? Partially: you can use composition (HAS-A) to include behavior from multiple "parent" classes. Delegation methods can forward calls to composed objects. This achieves code reuse without true multiple inheritance, at the cost of more boilerplate.
Q: What is the difference between instanceof and getClass()?
instanceof returns true for the class and all its subclasses. getClass() returns the exact runtime class. For Animal a = new Dog(): a instanceof Animal is true, a instanceof Dog is true, but a.getClass() == Animal.class is false (it is Dog.class). Use instanceof for polymorphic checks; use getClass() when exact type equality is required.
Q: What is the Composite pattern and how does it use polymorphism?
The Composite pattern represents part-whole hierarchies. Both individual objects and groups of objects implement the same interface. A Component interface; Leaf and Composite both implement it. Composite.operation() calls operation() on all its children (which can be Leaves or other Composites). Polymorphism allows treating individual items and groups uniformly.
Internal Links
Methodology applied to this articlelast verified 8 Jun 2026
- No fabricated salary numbers or success rates. If we quote a range, it's sourced.
- No noun-substituted templates. This article was not generated by swapping company names in a stock prompt.
- No paid placements, sponsored coaching links, or affiliate-shilled course pushes.
Explore this topic cluster
More resources in Interview Questions
Use the category hub to browse similar questions, exam patterns, salary guides, and preparation resources related to this topic.
Paid contributor programme
Sat this this year? Share your story, earn ₹500.
First-person experience reports help future candidates prep smarter. We pay verified contributors ₹500 via UPI per accepted story - with byline.
Submit your story →Ready to practice?
Take a free timed mock test
Put what you learned into practice. Our mock tests match the 2026 pattern with timer, navigator, reveal, and score breakdown. No signup.
Start Free Mock Test →More from PapersAdda
Accenture Interview Questions 2026 (with Answers for Freshers)
Capgemini Interview Questions 2026 (with Answers for Freshers)
HCLTech Interview Questions 2026 (TechBee + TGT, with Answers)
IBM Interview Questions 2026 (with Answers for Freshers)