issue 117apr 27mmxxvi
est. 2017
Sun, 27 Apr 2026
vol. IX · no. 117
PapersAdda
placement intelligence, since 2017
640+ briefs · 24 campuses · by reservation
verified offers · sourced from r/developersIndia
razorpay₹65.00 LPA· iit-d · sde-1google₹54.00 LPA· iiit-h · swe-imicrosoft₹49.50 LPA· iit-b · sdeatlassian₹38.00 LPA· nit-w · sde-1amazon₹44.20 LPA· bits-p · sde-1uber₹42.00 LPA· iit-kgp · sde-1razorpay₹65.00 LPA· iit-d · sde-1google₹54.00 LPA· iiit-h · swe-imicrosoft₹49.50 LPA· iit-b · sdeatlassian₹38.00 LPA· nit-w · sde-1amazon₹44.20 LPA· bits-p · sde-1uber₹42.00 LPA· iit-kgp · sde-1

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

36 min read
Interview Questions
Updated: 8 Jun 2026
Aditya Sharma
Aditya's Edit

PapersAdda 2026 Placement Cycle

By Aditya Sharma·Founder & Editor, PapersAdda

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:

  1. JVM/runtime dereferences the vptr to find the vtable
  2. Looks up sound() at slot 0 in the vtable
  3. 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):

  1. Same method name and parameter list (exact signature match)
  2. Return type: same type or a covariant type (subtype of original return type)
  3. Access modifier: cannot be more restrictive than parent; can be less restrictive
    • parent: protected -> child can be protected or public, NOT private
  4. Checked exceptions: cannot throw new or broader checked exceptions than the parent
  5. Cannot override: final methods, static methods (those are hidden, not overridden), private methods
  6. @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 final on 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)

FeatureAbstract ClassInterface
InstantiationCannot instantiateCannot instantiate
InheritanceSingle (extends)Multiple (implements)
State (fields)Instance variables allowedOnly constants (public static final)
ConstructorCan have constructorsNo constructors
MethodsAbstract + concrete + staticAbstract + default + static (Java 8+), private (Java 9+)
Access modifiersAnyMethods public by default

When to use abstract class:

  • Shared state among related classes (e.g., AbstractList has shared modCount)
  • 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 Duck and a Airplane can both be Flyable)
  • 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 static keyword
  • 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 static keyword
  • 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()

  • obj is declared as A but refers to a B object
  • greet() is overridden in B
  • Runtime dispatch: actual type B -> calls B.greet()
  • Output: Hello from B

Line 2: obj.create().greet()

  • obj.create(): runtime dispatch, actual type is B, calls B.create(), returns a B object
  • Return type of obj.create() in compile-time is A (because obj is declared as A)
  • .greet() on the returned object: actual type is B (returned by B.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:

  1. new Derived() called
  2. Base() constructor invoked first (Java inserts super() in Derived constructor)
  3. Inside Base(): init() is virtual -> calls Derived.init()
  4. At this point, Derived.y is still 0 (field initialization runs after super constructor)
  5. After Base() completes: Derived.y = 20 is set
  6. 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:

FeaturePython (duck typing)Java (nominal typing)
Type checkRuntime (AttributeError if missing)Compile time (type error)
Requires common parentNoYes (class or interface)
FlexibilityHighLower
Error detectionRuntimeCompile time
IDE supportLimitedFull

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):

  1. Reflexive: x.equals(x) must be true
  2. Symmetric: x.equals(y) iff y.equals(x)
  3. Transitive: if x.equals(y) and y.equals(z), then x.equals(z)
  4. Consistent: multiple calls return same result if no state change
  5. 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) or final (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)

KeywordPurposeMultiplicity
extendsInherit from a class or extend an interfaceOne (class extends class), multiple (interface extends interface)
implementsImplement an interfaceMultiple (class can implement many interfaces)

Class hierarchy rules:

  • Class extends class: single inheritance, inherit state + behavior
  • Class implements interface: multiple allowed, provides type contract
  • Interface extends interface: multiple allowed, extend the contract
  • Interface cannot implements anything
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):

InterfaceSignatureCommon 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, or non-sealed
  • final: cannot be extended further
  • sealed: can be extended but must also declare permits
  • non-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":

  1. s.accept(calc): dispatched based on runtime type of s (Circle or Rectangle) -> calls correct accept()
  2. visitor.visit(this): dispatched based on the concrete type of this inside accept() -> calls correct visit() 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:

  • Vehicle is abstract (cannot rent a generic "Vehicle")
  • Rentable is an interface (other rentable things, like trailers, may not be Vehicles)
  • CargoBearing is a separate interface (ISP: not all vehicles bear cargo)
  • rentalCost() is final template method in Vehicle (OCP: cannot accidentally override cost calculation)
  • additionalFees() is a hook: subclasses override to add fees (Truck cleaning fee)
  • dailyRate() in Truck is 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.


Methodology applied to this articlelast verified 8 Jun 2026
Sources used
Public exam-pattern documents, official recruiter pages, and verified candidate reports on r/developersIndia and LinkedIn.
Verification window
Page last edited 8 Jun 2026 by Aditya Sharma. Numbers and patterns sanity-checked against the most recent 2026 cycle drives we tracked.
What we did NOT do
  • 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.
Verification policy: /editorial-standards/. Found something incorrect? Submit a correction - we respond within 48 hours.

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

Share this guide: