OOP Concepts Interview Questions and Answers

Encapsulation, inheritance, polymorphism and design principles.

Practise 10 random 12 peer-reviewed questions
OOP Concepts Interview Syllabus & Preparation Strategy

Whether you are preparing for entry-level OOP Concepts interview questions for freshers or senior software engineer interview questions addressing concurrency, scalability, and system architecture, this track provides peer-reviewed model answers with syntax walkthroughs, edge cases, and practical interview tips.

1 What are the four pillars of object-oriented programming? Easy

Object-oriented programming rests on four pillars:

  • Encapsulation: keep state private and expose behaviour through methods, so invariants cannot be broken from outside.
  • Abstraction: model only the essential behaviour, hiding implementation detail behind an interface or abstract type.
  • Inheritance: derive a specialised type from a base type to reuse and extend behaviour.
  • Polymorphism: call the same method on different types and let the runtime pick the right implementation.
interface Shape { double area(); }
class Circle implements Shape {
    private final double r;
    Circle(double r) { this.r = r; }
    public double area() { return Math.PI * r * r; }
}

Here Circle encapsulates r, implements the abstraction Shape and participates in polymorphism. In practice inheritance is the least important and most abused pillar; composition and interfaces usually age better. The SOLID principles refine these ideas to keep designs maintainable as systems grow.

2 What is the difference between an abstract class and an interface? Easy

An interface declares a contract: a set of method signatures with no state that any type can implement. A class can implement many interfaces, which models capability. An abstract class is a partially implemented base class that can hold fields, constructors and concrete methods; a class can extend only one.

interface Flyer { void fly(); }
abstract class Bird {
    protected String name;
    Bird(String name) { this.name = name; }
    abstract void sing();
    void sleep() { System.out.println("sleeping"); }
}

Use an interface when unrelated types must share a capability and when you want multiple inheritance of type. Use an abstract class when related types share code and state, and you want to force subclasses to fill in specific steps. Many designs use both: an abstract class implements a primary interface and provides default behaviour, while callers depend only on the interface. Prefer interfaces at module boundaries and abstract classes internally.

3 What is encapsulation and why does it matter? Easy

Encapsulation means bundling data with the operations that act on it and restricting direct access to the internals. The class exposes a deliberate public API and keeps fields private, so it can enforce invariants rather than trusting every caller.

class BankAccount {
    private long balance;
    public BankAccount(long initial) {
        if (initial < 0) throw new IllegalArgumentException();
        this.balance = initial;
    }
    public void deposit(long amount) {
        if (amount <= 0) throw new IllegalArgumentException();
        balance += amount;
    }
    public long getBalance() { return balance; }
}

Because balance is private, code cannot set it to a negative value or bypass validation. Encapsulation reduces coupling: you can change the internal representation, add caching or logging, or swap a field for a computed value without breaking callers. It is not the same as adding getters and setters to every field, which leaks implementation just as badly as public fields when the accessors are unthinking.

4 What is the Liskov Substitution Principle and how is it violated? Medium

The Liskov Substitution Principle says objects of a subtype must be usable anywhere the supertype is expected, without the caller knowing or behaving differently. Subtypes may strengthen guarantees, never weaken them.

The classic violation is Square extends Rectangle. Setting width on a rectangle implies height is unchanged, but a square must also change height, so code that mutates a rectangle breaks when handed a square.

void resize(Rectangle r) {
    r.setWidth(5);
    r.setHeight(4);
    assert r.area() == 20; // fails for Square
}

Another violation is a subtype that throws for a method the base promises to support, or that returns a narrower result. Fixes include favouring composition, extracting a common interface with only genuinely shared behaviour, or making the hierarchy immutable. LSP is really about honouring the base type's preconditions, postconditions and invariants.

5 When should you prefer composition over inheritance? Medium

Inheritance is an is-a relationship: Dog extends Animal because every dog is an animal. Composition is a has-a relationship: Car holds an Engine. Inheritance gives reuse plus polymorphism but couples the child tightly to the parent's implementation. A change in the base class can silently break subclasses, and a subclass inherits everything, needed or not.

Composition builds behaviour from parts, so you can swap, decorate or test those parts independently.

class Logger { void log(String m) { System.out.println(m); } }
class Service {
    private final Logger logger = new Logger();
    void run() { logger.log("start"); }
}

The guidance to prefer composition means use inheritance only when the subtype truly is a substitutable kind of the base and the base was designed for extension. Otherwise inject collaborators behind interfaces. This keeps hierarchies shallow, avoids fragile base classes and makes behaviour easy to vary at runtime.

6 What is polymorphism and what forms does it take? Medium

Polymorphism lets one interface be used by many implementations, so callers depend on a contract rather than a concrete type.

Subtype or runtime polymorphism: a base reference points at a derived object and the override is chosen at runtime through dynamic dispatch.

Ad-hoc or compile-time polymorphism: overloading picks a method by argument types at compile time.

Parametric polymorphism: generics let one definition work for any type while keeping type safety, for example List<T>.

abstract class Animal { abstract String speak(); }
class Cat extends Animal { String speak() { return "meow"; } }
class Dog extends Animal { String speak() { return "woof"; } }
Animal a = new Dog();
a.speak(); // woof, resolved at runtime

The benefit is extensibility: new types can be added without editing callers as long as they honour the contract. The cost is that dispatch is indirect, which matters in hot loops and when debugging.

7 Design a shape hierarchy so new shapes can be added without changing existing code. Medium

Depend on an abstraction and let each shape compute its own values.

interface Shape {
    double area();
    double perimeter();
}
class Circle implements Shape {
    private final double r;
    Circle(double r) { this.r = r; }
    public double area() { return Math.PI * r * r; }
    public double perimeter() { return 2 * Math.PI * r; }
}
class Rectangle implements Shape {
    private final double w, h;
    Rectangle(double w, double h) { this.w = w; this.h = h; }
    public double area() { return w * h; }
    public double perimeter() { return 2 * (w + h); }
}
double total(Shape[] shapes) {
    double sum = 0;
    for (Shape s : shapes) sum += s.area();
    return sum;
}

total never changes when a Triangle is added, which is the open-closed principle. Validate arguments in constructors so invalid shapes cannot exist. Avoid a type field with a big switch, because that reintroduces exactly the coupling the abstraction removed.

8 What does the Single Responsibility Principle actually mean? Medium

The Single Responsibility Principle states that a class should have one reason to change, meaning it should serve one actor or concern. It is about change, not size: a small class can still have two reasons to change if it mixes two concerns.

A class that loads an order from a database, calculates tax, formats an invoice and sends email has four reasons to change. A database change, a tax rule change, a layout change and an email provider change each force edits to the same file.

class Order { BigDecimal total() { ... } }
class TaxCalculator { BigDecimal forOrder(Order o) { ... } }
class InvoiceRenderer { String render(Order o) { ... } }
class EmailSender { void send(Invoice i) { ... } }

The payoff is easier testing, clearer ownership and a smaller blast radius when requirements change. Applied dogmatically it can produce hundreds of anemic classes, so group responsibilities that genuinely change together for the same reason.

9 What is the difference between overloading and overriding? Medium

Overloading and overriding are different mechanisms that are often confused because both involve the same method name.

Overloading is compile-time polymorphism: several methods share a name but differ in their parameter list. The compiler picks one from the static types of the arguments.

void print(int x)    { }
void print(String s) { }

Overriding is runtime polymorphism: a subclass provides its own version of a method inherited from the superclass with the same signature. The runtime picks the implementation from the actual object type.

class A { void hi() { System.out.println("A"); } }
class B extends A { @Override void hi() { System.out.println("B"); } }
A a = new B(); a.hi(); // B

Overloading cannot be decided by return type alone, and overriding must not reduce visibility or throw broader checked exceptions. Overloading is resolved on the declared type; overriding on the runtime type.

10 How does the Dependency Inversion Principle change a design? Medium

The Dependency Inversion Principle has two parts: high-level policy should not depend on low-level detail, and abstractions should not depend on details; details should depend on abstractions. In practice, business logic defines the interfaces it needs and infrastructure implements them.

Without DIP, an OrderService that directly instantiates MySqlOrderRepository is welded to MySQL and hard to test. With DIP, the service depends on an OrderRepository interface, and the concrete database class is injected.

interface OrderRepository { void save(Order o); }
class OrderService {
    private final OrderRepository repo;
    OrderService(OrderRepository repo) { this.repo = repo; }
    void place(Order o) { repo.save(o); }
}

This inverts the usual direction of dependency and enables unit tests with in-memory fakes. It is closely tied to dependency injection as the mechanism and to hexagonal architecture, which keeps the domain free of framework code.

11 What is the diamond problem and how do languages solve it? Hard

The diamond problem occurs when a class inherits from two classes that share a common ancestor, so the same base members are inherited along two paths. Ambiguity arises over which copy or implementation is used, and over duplicated state.

      A
     / \
    B   C
     \ /
      D

Languages solve it differently. C++ allows multiple inheritance and requires disambiguation with virtual inheritance; without virtual, D has two A subobjects. Java and C# forbid multiple inheritance of classes but allow a class to implement many interfaces. Java 8 and later let interfaces provide default methods, and the compiler forces you to override when two defaults conflict. Python uses C3 linearisation, the method resolution order, so attributes resolve in a deterministic order. The common lesson is to favour interfaces and composition over multiple inheritance of implementation.

12 How would you refactor a god class into a maintainable design? Hard

A god class is large, does many unrelated things, has many dependencies and is touched by most changes. Treat the refactor as a series of safe, behaviour-preserving steps, backed by tests.

  1. Write characterisation tests around the public API so you have a safety net.
  2. Identify distinct responsibilities: persistence, business rules, validation, formatting, notifications.
  3. Extract cohesive groups into collaborator classes behind interfaces, moving one concern at a time and running tests after each move.
  4. Replace direct construction of collaborators with injected dependencies so they can be faked.
  5. Introduce a facade if callers should keep a single entry point, then remove dead code.
before: OrderManager (everything)
after:  OrderService + Pricing + Repository + Notifier

Work incrementally and commit often; a big-bang rewrite is risky. Watch for hidden state passed between methods, which is the main obstacle to extraction. Measure progress with coupling metrics and cyclomatic complexity, and use the tests, not intuition, to prove behaviour is unchanged.

Frequently Asked Questions About OOP Concepts Interviews

What do hiring managers evaluate in OOP Concepts technical rounds?

Technical interviewers look for foundational fluency, idiomatic syntax, clarity when communicating complex logic, and awareness of performance trade-offs (e.g. memory footprint, render performance, and network latency) in production environments.

What are the best interview tips for practicing OOP Concepts questions?

Use active recall: summarize each answer in your own words before revealing the model solution. Focus on explaining why a certain approach is chosen rather than just memorizing code syntax.