OOP Concepts Interview Questions and Answers
Encapsulation, inheritance, polymorphism and design principles.
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 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.
2 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.
3 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.
4 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.
5 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.
6 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.
7 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.
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.