OOP Interview Questions
Object-Oriented Programming (OOP) interviews assess your understanding of fundamental concepts like encapsulation, inheritance, polymorphism, and abstraction, as well as your ability to apply design patterns and write clean, maintainable code. These questions are common for software engineering roles at all levels, from junior to senior, but especially for senior positions where design and architecture decisions matter. Interviewers expect you to explain concepts clearly, relate them to real-world examples, and solve coding problems that demonstrate OOP principles in action.
What OOP interviews cover
Core Principles
Questions on encapsulation, inheritance, polymorphism, and abstraction with examples and real-world analogies.
Design Patterns
Common patterns like Singleton, Factory, Observer, and Strategy, their implementations and when to use them.
Class Design & Relationships
Inheritance vs composition, abstract classes vs interfaces, coupling/cohesion, SOLID principles.
Hands-On Coding Problems
Implementing class hierarchies, designing a parking lot or a library system, and refactoring procedural code to OOP.
Sample OOP interview questions
- Explain the four pillars of OOP and give real-world examples.What a strong answer covers
- Encapsulation bundles data and methods, hiding internal state.
- Abstraction simplifies complex reality by modeling classes appropriate to the problem.
- Inheritance creates parent-child class relationships for code reuse.
- Polymorphism allows objects of different types to respond to the same interface.
View a sample answer
The four pillars of OOP are encapsulation, abstraction, inheritance, and polymorphism. Encapsulation means hiding an object's internal state and requiring all interaction through its methods. For example, a BankAccount class keeps the balance private and exposes deposit/withdraw methods. Abstraction means hiding implementation details and showing only essential features: a Car class provides a drive() method without exposing engine internals. Inheritance allows a class to derive from a parent, reusing code: Dog extends Animal, inheriting breathe(). Polymorphism lets objects of different classes be treated through a common interface—e.g., a draw() method on Shape can be called on Circle or Rectangle, each implementing its own version. Trade-offs: inheritance can lead to tight coupling; encapsulation may add boilerplate. Real-world example: a UI framework where Button extends Component (inheritance), and each Button handles click events polymorphically through an onClick interface.
- What is the difference between an abstract class and an interface? When would you use each?What a strong answer covers
- Abstract classes can have constructors, fields, and both abstract and concrete methods.
- Interfaces only declare method signatures (before Java 8) and can have default/static methods (Java 8+).
- Abstract classes are for classes that share a common base; interfaces define contracts across hierarchies.
- Use abstract class when subclasses share state or logic; use interface for diverse classes needing a common capability.
View a sample answer
The key difference between an abstract class and an interface lies in their purpose and capabilities. An abstract class can have instance variables, constructors, and both abstract and concrete methods, allowing it to provide partial implementation and shared state. An interface traditionally only declared method contracts (abstract methods in Java), but since Java 8, it can also have default and static methods. However, interfaces cannot hold instance state (fields are static final). You use an abstract class when you have a base class that logically groups related classes—e.g., an Animal abstract class with a shared name field and a concrete method sleep(), leaving speak() abstract. You use an interface to define a capability that can be implemented by unrelated classes—e.g., Flyable interface for both Bird and Plane. A pitfall is overusing inheritance when composition or interfaces would be more flexible. In modern design, prefer interfaces for abstraction and use abstract classes sparingly.
- Implement a Singleton pattern in Java (or your language of choice) and discuss thread-safety.What a strong answer covers
- Singleton ensures only one instance of a class per JVM.
- Thread-safety is critical to prevent multiple instances in concurrent access.
- Double-checked locking with volatile is a common approach.
- Enum-based singleton is the most robust, preventing reflection and serialization issues.
View a sample answer
The Singleton pattern restricts a class to a single instance and provides a global point of access. In Java, implementing a thread-safe singleton is crucial because naïve lazy initialization can lead to multiple instances under concurrent access. The double-checked locking pattern uses a volatile variable and synchronized block to ensure only one thread creates the instance. However, this can be error-prone; a simpler and more robust approach is the enum singleton, which inherently provides serialization and reflection safety. The enum is the preferred way as it's concise and guarantees a single instance even across serialization/deserialization. Trade-offs: Singleton introduces a global state, making testing harder and violating single responsibility if it also contains business logic. It should be used sparingly, often for logging, configuration, or thread pools.
Reference solutionjava public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } // Alternative: Enum Singleton (best practice) public enum SingletonEnum { INSTANCE; // methods here } // Time: O(1) amortized; Space: O(1) - Design a class hierarchy for shapes (Circle, Rectangle) with area calculation, demonstrating polymorphism.What a strong answer covers
- Abstract Shape class with area() method.
- Circle extends Shape, stores radius, overrides area().
- Rectangle extends Shape, stores width and height, overrides area().
- Demonstrates polymorphism when calling area() on Shape references.
View a sample answer
This design uses an abstract Shape class defining an area() contract, then Circle and Rectangle extend it with their own implementations. Polymorphism is demonstrated when a Shape reference holds a Circle or Rectangle and the correct area() is called via dynamic dispatch. This adheres to the Open-Closed principle: new shapes can be added without modifying existing code. A common pitfall is forgetting to use the @Override annotation, which can lead to unintentional method overloading. Additionally, if shapes share more logic (like color), consider moving it to the base class as a concrete field or method. This hierarchy is simple but extensible.
Reference solutionjava // Abstract base class public abstract class Shape { public abstract double area(); } // Circle subclass public class Circle extends Shape { private double radius; public Circle(double radius) { this.radius = radius; } @Override public double area() { return Math.PI * radius * radius; } } // Rectangle subclass public class Rectangle extends Shape { private double width, height; public Rectangle(double width, double height) { this.width = width; this.height = height; } @Override public double area() { return width * height; } } // Usage demonstrating polymorphism public class Main { public static void main(String[] args) { Shape[] shapes = {new Circle(5), new Rectangle(4, 6)}; for (Shape s : shapes) { System.out.println("Area: " + s.area()); } } } // Time: O(1) per area call; Space: O(1) - How does inheritance lead to tight coupling? Prefer composition over inheritance – explain with an example.What a strong answer covers
- Inheritance creates tight coupling between parent and child classes.
- Changes to parent can break child classes (fragile base class problem).
- Composition uses interfaces and delegation to reduce coupling.
- Prefer composition over inheritance for behavioral reuse.
View a sample answer
Inheritance leads to tight coupling because a subclass depends on the internal implementation of its superclass. Any change in the superclass, such as adding a new method or altering a protected field, can inadvertently break subclasses—this is known as the fragile base class problem. For example, if a Bird class has a fly() method, and you create a Penguin subclass that overrides it to throw an exception, you violate Liskov Substitution. Composition avoids this by assembling behaviors through interfaces and delegation. For instance, instead of having a Duck class extend a Flyable superclass, you can define a FlyBehavior interface with implementations like FlyWithWings and FlyNoWay. The Duck class holds a reference to a FlyBehavior and delegates the fly() call. This allows you to change behavior at runtime and reuse behaviors across unrelated classes. The principle 'Favor composition over inheritance' reduces coupling and increases flexibility. A trade-off is that composition may lead to more objects and slightly more complex design, but it's generally worth it for maintainability.
- Write code to demonstrate how you would implement a strategy pattern to calculate shipping costs.What a strong answer covers
- Defines a family of algorithms, encapsulates each, and makes them interchangeable.
- ShippingStrategy interface declares calculateCost(weight, distance).
- Concrete strategies: GroundShipping, AirShipping, etc.
- Order class uses a ShippingStrategy to compute shipping cost.
View a sample answer
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. In the shipping cost example, we create a ShippingStrategy interface with a calculateCost method. Concrete strategies like GroundShipping, AirShipping, and ExpressShipping implement the interface with different formulas. The Order class (context) holds a reference to a ShippingStrategy and delegates cost calculation to it. This allows changing the algorithm at runtime via setShippingStrategy, and new shipping methods can be added without modifying Order or existing strategies—adhering to the Open-Closed principle. A pitfall is overusing the pattern, leading to many small classes. Also, if strategies share common logic, consider using an abstract base class or default methods.
Reference solutionjava // Strategy interface public interface ShippingStrategy { double calculateCost(double weight, double distance); } // Concrete strategies public class GroundShipping implements ShippingStrategy { @Override public double calculateCost(double weight, double distance) { return weight * 0.5 + distance * 0.1; } } public class AirShipping implements ShippingStrategy { @Override public double calculateCost(double weight, double distance) { return weight * 1.5 + distance * 0.5; } } public class ExpressShipping implements ShippingStrategy { @Override public double calculateCost(double weight, double distance) { return (weight * 2.0 + distance * 0.8) * 1.2; } } // Context class public class Order { private ShippingStrategy shippingStrategy; private double weight; private double distance; public Order(ShippingStrategy shippingStrategy, double weight, double distance) { this.shippingStrategy = shippingStrategy; this.weight = weight; this.distance = distance; } public void setShippingStrategy(ShippingStrategy shippingStrategy) { this.shippingStrategy = shippingStrategy; } public double getShippingCost() { return shippingStrategy.calculateCost(weight, distance); } } // Usage public class Main { public static void main(String[] args) { Order order = new Order(new GroundShipping(), 10, 100); System.out.println("Ground: " + order.getShippingCost()); order.setShippingStrategy(new AirShipping()); System.out.println("Air: " + order.getShippingCost()); } } // Time: O(1); Space: O(1) - What is the Liskov Substitution Principle? Provide a code example that violates it.What a strong answer covers
- LSP states that objects of a subclass must be replaceable by objects of its superclass without altering correctness.
- Violation occurs when subclass changes expected behavior of superclass methods.
- Classic violation: Square extending Rectangle with inconsistent setters.
- Prefer composition or use a common abstract class that does not enforce invariants.
View a sample answer
The Liskov Substitution Principle (LSP) states that if S is a subtype of T, then objects of type T should be replaceable with objects of type S without affecting the correctness of the program. A classic violation is the Square-Rectangle problem. A Square extends Rectangle, but to maintain square invariant, it overrides setWidth and setHeight to set both dimensions equally. A client expecting a Rectangle might set width=5 and height=10, expecting area=50, but because the actual object is a Square, the area becomes 100 (since both become 10). This breaks the client's assumption. To fix, avoid such a hierarchy: instead, have both Square and Rectangle implement a common Shape interface that doesn't include mutating setters, or use composition. LSP violations lead to subtle bugs and reduce reusability. Always design subclasses that do not weaken or change the preconditions/postconditions of the base class.
Reference solutionjava // Violation of LSP: Square extends Rectangle class Rectangle { protected int width, height; public void setWidth(int width) { this.width = width; } public void setHeight(int height) { this.height = height; } public int getArea() { return width * height; } } class Square extends Rectangle { @Override public void setWidth(int width) { this.width = width; this.height = width; // enforce square invariant } @Override public void setHeight(int height) { this.width = height; this.height = height; } } // Client code that expects Rectangle behavior public class Main { public static void main(String[] args) { Rectangle r = new Square(); r.setWidth(5); r.setHeight(10); System.out.println("Expected area: 50, got: " + r.getArea()); // 100? Actually 10*10=100 (Square overrides) // Violation: client assumes width*height, but square changes height to match width. } } // Fix: Don't have Square extend Rectangle; instead have both extend Shape with no setters, or use composition. - Design a restaurant order system with classes for MenuItem, Order, and Payment, ensuring good OOP design.What a strong answer covers
- MenuItem is abstract with name and price.
- Order contains a list of MenuItems, calculates total, and can add/remove items.
- Payment is abstract with a processPayment method, subclassed into CreditCard, Cash, etc.
- Good OOP: single responsibility, open-closed, encapsulation.
View a sample answer
The restaurant order system is designed with clear separation of concerns: MenuItem is an abstract base for all items, allowing easy addition of new item types (Open-Closed). Order manages a list of MenuItems, handling aggregation and total calculation, and delegates payment to a Payment strategy. Payment is abstract with concrete implementations like CreditCardPayment and CashPayment, enabling different payment methods without modifying Order. This design follows Single Responsibility: each class has one reason to change. Encapsulation is maintained by keeping fields private and exposing necessary methods. A pitfall would be coupling Order directly to payment details; instead, we set a Payment object. Also, if menu items have modifiers (e.g., size), consider using the Decorator pattern. Trade-offs include increased number of classes, but it achieves flexibility and maintainability.
Reference solutionjava import java.util.ArrayList; import java.util.List; // Abstract MenuItem public abstract class MenuItem { private String name; private double price; public MenuItem(String name, double price) { this.name = name; this.price = price; } public String getName() { return name; } public double getPrice() { return price; } } // Concrete items public class Drink extends MenuItem { public Drink(String name, double price) { super(name, price); } } public class MainCourse extends MenuItem { public MainCourse(String name, double price) { super(name, price); } } // Order class public class Order { private List<MenuItem> items = new ArrayList<>(); private Payment payment; public void addItem(MenuItem item) { items.add(item); } public void removeItem(MenuItem item) { items.remove(item); } public double getTotal() { return items.stream().mapToDouble(MenuItem::getPrice).sum(); } public void setPayment(Payment payment) { this.payment = payment; } public boolean pay() { return payment.processPayment(getTotal()); } } // Abstract Payment public abstract class Payment { public abstract boolean processPayment(double amount); } public class CreditCardPayment extends Payment { private String cardNumber; public CreditCardPayment(String cardNumber) { this.cardNumber = cardNumber; } @Override public boolean processPayment(double amount) { // process credit card System.out.println("Processing credit card payment of $" + amount); return true; } } public class CashPayment extends Payment { @Override public boolean processPayment(double amount) { System.out.println("Processing cash payment of $" + amount); return true; } } // Usage public class Restaurant { public static void main(String[] args) { Order order = new Order(); order.addItem(new MainCourse("Steak", 25.0)); order.addItem(new Drink("Soda", 2.5)); order.setPayment(new CreditCardPayment("1234-5678")); order.pay(); } } // Time: O(n) for total; Space: O(n) for items
How to prepare
- Master the definitions and examples for each OOP principle; be ready to explain with analogies.
- Practice designing systems on a whiteboard or using a code editor, focusing on class responsibilities and relationships.
- Review common design patterns and be able to implement them from scratch in your language.
- Understand trade-offs: inheritance vs composition, tight vs loose coupling, and when to use abstract vs interface.
- Prepare for hands-on coding: write small OOP programs and be comfortable explaining your design choices.
Frequently asked questions
What are the most common OOP interview questions?
Common questions include explain the four pillars, difference between abstract class and interface, design a class hierarchy, and implement a design pattern like Singleton.
How should I prepare for OOP coding problems?
Practice designing small systems like a parking lot, library, or e-commerce cart. Focus on class structure, encapsulation, and using interfaces.
Do I need to know design patterns for an OOP interview?
Yes, especially for senior roles. Know the most common ones: Singleton, Factory, Strategy, Observer, and Decorator.
What is the difference between inheritance and composition?
Inheritance creates an 'is-a' relationship, while composition creates a 'has-a' relationship. Favor composition for flexibility and loose coupling.
How do I explain OOP concepts clearly?
Use real-world analogies: a car as an object with properties (color, model) and methods (start, stop), or a zoo animals hierarchy to explain polymorphism.
Practice OOP questions with instant AI feedback
Upload your resume, get a personalized mock interview, and see exactly what to improve — free to start.