Skip to content

Inheritance Interview Questions & Answers

16 questions Updated 2026-06-19 Share:

Java inheritance interview questions — extends and super, single vs multiple inheritance, the Object superclass, is-a vs has-a, composition over inheritance, upcasting and downcasting, Liskov substitution and SOLID.

Read the in-depth guideJava OOP — Inheritance, Polymorphism, Abstraction & Encapsulation(opens in new tab)
16 of 16
  • Encapsulation — bundle data with the methods that operate on it and hide internal state behind a public API (private fields + getters/setters).
  • Abstraction — expose what an object does, hide how, via interfaces and abstract classes.
  • Inheritance — a subclass reuses and extends a superclass's members (extends).
  • Polymorphism — one reference type, many runtime forms; the actual object's overridden method is called.
class Animal { String sound() { return "..."; } }
class Dog extends Animal { String sound() { return "Woof"; } } // inheritance + override
Animal a = new Dog();   // polymorphism
a.sound();              // "Woof"

A good one-liner: encapsulation hides data, abstraction hides complexity, inheritance reuses behavior, polymorphism varies it.

Inheritance lets a subclass acquire the fields and methods of a superclass with extends, modeling an is-a relationship and enabling reuse + polymorphism.

class Vehicle { void start() { } }
class Car extends Vehicle { void honk() { } } // Car is-a Vehicle

Java allows single inheritance of classes only — a class can extend exactly one superclass (to avoid the "diamond problem"). It can implement multiple interfaces, which is how Java gets multiple-type flexibility without multiple class inheritance. final classes can't be extended at all.

java.lang.Object is the root of every class hierarchy — if you don't extends anything, you implicitly extend Object. So every object has its methods, the most important being:

  • equals(Object) / hashCode() — value equality and hashing.
  • toString() — string representation.
  • getClass() — the runtime class.
  • clone(), finalize() (deprecated), and the wait/notify family for threading.
class Foo { }            // implicitly: class Foo extends Object
Object o = new Foo();
o.toString();            // "Foo@1b6d3586" by default

Rule of thumb: because everything is-an Object, an Object reference (or Object[]) can hold anything — the basis of pre-generics collections.

  • Single — one subclass, one superclass. ✅
  • Multilevel — a chain (C extends B extends A). ✅
  • Hierarchical — many subclasses of one parent. ✅
  • Multiple inheritance of classes — extending two classes. ❌ (not allowed)
  • Multiple inheritance of types — implementing many interfaces. ✅
class A { }
class B extends A { }            // single
class C extends B { }            // multilevel
class D extends A { }            // hierarchical (with B)
class E implements I1, I2 { }    // multiple interfaces — OK

Rule of thumb: Java forbids multiple class inheritance but allows multiple interface inheritance — you get many capabilities, but a single implementation lineage.

To avoid the diamond problem: if B and C both extended A and overrode a method, and D extended both B and C, the compiler couldn't tell which version D inherits. C++ allows it and pays for it with complexity (virtual inheritance).

// Hypothetical — illegal in Java:
// class D extends B, C { }   // which greet() does D get?
interface B { default String greet() { return "B"; } }
interface C { default String greet() { return "C"; } }
class D implements B, C {
  public String greet() { return B.super.greet(); } // YOU resolve it
}

Java permits multiple interface inheritance because conflicting default methods force the class to resolve the ambiguity explicitly. Rule of thumb: Java trades raw power for predictability.

super refers to the immediate superclass. It does two jobs: call the parent constructor (super(args), must be the first statement) and call an overridden parent method or access a parent field (super.method()).

class Animal {
  Animal(String name) { }
  void describe() { System.out.println("an animal"); }
}
class Dog extends Animal {
  Dog(String name) {
    super(name);              // chain to parent constructor
  }
  @Override void describe() {
    super.describe();         // reuse parent behavior
    System.out.println("...specifically a dog");
  }
}

If you don't call super(...) explicitly, the compiler inserts a no-arg super() — which fails to compile if the parent has no no-arg constructor.

No — constructors are not inherited. But every subclass constructor must invoke a superclass constructor first, via an explicit super(...) or an implicit no-arg super() the compiler inserts.

class Base {
  Base(int id) { }            // no no-arg constructor
}
class Sub extends Base {
  Sub() { super(1); }         // MUST call super(int) explicitly
  // Sub() { }                // would NOT compile — implicit super() missing
}

So if a parent defines only parameterized constructors, the child is forced to call one. Rule of thumb: inheritance reuses methods and fields, not constructors — the child builds the parent's slice of itself by chaining up.

  • Upcasting — treating a subclass object as its superclass type. Always safe, implicit. Enables polymorphism.
  • Downcasting — casting a superclass reference back to a subclass. Needs an explicit cast and can throw ClassCastException if the object isn't actually that subtype — guard it with instanceof.
Animal a = new Dog();          // upcast — implicit, safe
Dog d = (Dog) a;               // downcast — explicit
if (a instanceof Cat c) {      // pattern: test before casting
  c.meow();
}

Rule of thumb: upcast freely to program to the supertype; downcast rarely, and only after an instanceof check.

instanceof tests whether an object is an instance of a given type (or subtype), returning a boolean. Since Java 16, pattern matching lets you bind a variable in the same expression, avoiding a separate cast.

Object o = "hello";
if (o instanceof String) { }            // classic
if (o instanceof String s) {            // pattern matching
  System.out.println(s.length());       // s is already a String
}

null instanceof X is always false. Rule of thumb: heavy instanceof chains are often a smell — prefer polymorphism (let each type implement the method) unless you're at a true type boundary (deserialization, equals).

Inheritance (is-a) tightly couples a subclass to its parent's implementation; changes to the base can silently break subclasses (the "fragile base class" problem). Composition (has-a) builds behavior by holding other objects and delegating — looser coupling, more flexible.

// inheritance misused: a Stack is-a List? leaks all List methods
class Stack<T> extends ArrayList<T> { }

// composition: Stack HAS a list, exposes only stack operations
class Stack<T> {
  private final List<T> items = new ArrayList<>();
  void push(T t) { items.add(t); }
  T pop() { return items.remove(items.size() - 1); }
}

Guideline ("favor composition over inheritance"): use inheritance only for a true is-a with a stable base designed for extension; otherwise compose. It also sidesteps single-inheritance limits.

  • is-a -> inheritance. A Car is a Vehicle -> class Car extends Vehicle.
  • has-a -> composition/aggregation. A Car has an Engine -> the Car holds an Engine field.
class Engine { }
class Car extends Vehicle {   // is-a Vehicle
  private final Engine engine = new Engine(); // has-a Engine
}

Modeling tip: if you can't truthfully say "X is a Y," don't use inheritance — reach for has-a (composition) instead. Misusing is-a (e.g. Stack extends Vector) is a classic design smell.

Both are has-a, differing in lifecycle ownership:

  • Composition — the part can't exist without the whole; the whole owns and creates it (a House and its Rooms). Strong ownership.
  • Aggregation — the part can exist independently and may be shared (a Team and its Players; players outlive the team).
class House {
  private final Room room = new Room();   // composition: created/owned here
}
class Team {
  private final List<Player> players;      // aggregation: passed in, shared
  Team(List<Player> players) { this.players = players; }
}

Rule of thumb: "owns and destroys together" = composition; "references but doesn't own" = aggregation.

Three ways, by strength:

  • final class — outright bans extends (e.g. String).
  • private/package-private constructors — no outside subclass can call super(...), so it can't be extended elsewhere.
  • sealed class (Java 17+) — allows only a named permits list of subclasses.
final class Money { }                          // no subclasses at all
sealed class Shape permits Circle, Square { }  // only these two

Reasons: protect invariants (an immutable class shouldn't be subclassed into mutability), security, and clearer design. Rule of thumb: design for inheritance and document it, or prohibit it with final/sealed.

  • Cohesion — how focused a class is on a single responsibility. High cohesion is good: a UserValidator that only validates users.
  • Coupling — how dependent classes are on each other's internals. Low coupling is good: classes interact through small, stable interfaces.
// low coupling: depends on an interface, not a concrete logger
class OrderService {
  private final Logger log;            // injected abstraction
  OrderService(Logger log) { this.log = log; }
}

The goal of good OO design is high cohesion, low coupling — classes that do one thing well and lean on each other as little as possible, which makes systems easier to change and test.

The L in SOLID: a subtype must be usable anywhere its supertype is expected, without breaking the program's correctness. A subclass that strengthens preconditions, weakens postconditions, or throws on inherited operations violates it.

// Classic violation: Square is-a Rectangle?
class Rectangle { void setWidth(int w){} void setHeight(int h){} }
class Square extends Rectangle {
  void setWidth(int w){ /* also sets height — breaks setWidth's contract */ }
}
// code that sets width then expects height unchanged now fails

The fix is usually composition or a shared abstraction, not inheritance. Rule of thumb: if a subclass can't honor everything the parent promises, it shouldn't extend it.

Five OO design principles for maintainable code:

  • S — Single Responsibility: a class has one reason to change.
  • O — Open/Closed: open for extension, closed for modification.
  • L — Liskov Substitution: subtypes must be usable wherever their base is.
  • I — Interface Segregation: prefer small, specific interfaces over fat ones.
  • D — Dependency Inversion: depend on abstractions, not concretions.
// Dependency Inversion: high-level code depends on an interface
interface Repository { void save(Order o); }
class OrderService {
  private final Repository repo;       // not a concrete DB class
  OrderService(Repository repo) { this.repo = repo; }
}

Liskov is the one interviewers probe most: a subclass that throws on, or weakens, an inherited method (the Square extends Rectangle problem) violates it.

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel