Articles
OOPs Concepts Interview Questions: Answering the Four Pillars the Way Companies Ask
OOPs concepts answered the way interviewers probe them: encapsulation vs abstraction, inheritance vs composition, polymorphism, and the SOLID follow-ups.
"Explain OOPs concepts" sounds like a warm-up, and that is exactly why it is dangerous. The interviewer is not checking whether you can recite four words. They are checking whether you can tell encapsulation from abstraction without hand-waving, whether you reach for composition before inheritance, and whether you can trace which method actually runs when a Shape reference points at a Circle. This page answers each concept the way interviewers probe it, using real questions asked at Microsoft, Uber, Apple, HBO, Duolingo, SoFi, and Intercontinental Exchange. If your interview asks you to design with these concepts rather than explain them, this is the "explain it" page; the closing section points you to the guides that cover the "design it" half.
Key Takeaways
- Encapsulation protects an object's state and invariants; abstraction hides mechanism behind a contract. If your answer could describe both, it describes neither; separate them with one concrete example each.
- When an interviewer asks "inheritance or composition?", the expected answer is composition, and the expected follow-up is you naming the one case where inheritance is still right (a stable, true is-a relationship that satisfies Liskov substitution).
- Compile-time polymorphism (overloading, templates) is resolved from the declared type; runtime polymorphism (overriding) is resolved from the actual object. Python skips the first kind entirely: a second
defwith the same name silently replaces the first. - Interface vs abstract class is a question about state and evolution, not syntax. Since Java 8, "interfaces can't have method bodies" is a wrong answer.
- The definitions buy you two minutes. The next twenty are SOLID follow-ups applied to code you just wrote, so prepare each pillar with a probe-and-response, not a paragraph.
How companies actually ask about OOPs concepts
Asked at Microsoft — Explain OOP design and API rollout A backend-focused round at Microsoft skipped algorithms and asked the candidate to walk through adding a new piece of business logic to an existing service using object-oriented design: how they would pick the core domain objects, assign responsibilities, keep orchestration separate from business rules, and make the result testable and extensible. The same round then pivoted to evolving a public API that already had consumers.
Notice what is missing from that prompt: the word "pillar". Nobody at Microsoft asked "what are the four pillars of OOP?", yet the question is entirely about them. Identifying domain objects is abstraction. Assigning responsibilities and hiding state is encapsulation. "Extensible" is polymorphism plus the open/closed principle. The interviewer expects you to use the vocabulary while solving a problem, not to define it in a vacuum.
That said, the definitional version does get asked, especially in first rounds and at companies that screen fundamentals hard. When it does, the four pillars are:
- Encapsulation — bundle state with the methods that maintain its invariants, and deny direct access to that state.
- Abstraction — expose what an object does through a minimal contract; hide how it does it.
- Inheritance — derive a new type from an existing one, inheriting its contract and (usually) implementation.
- Polymorphism — let one call site work across many concrete types, resolved either at compile time or at run time.
The escalation pattern is consistent across companies: definition → distinction ("how is abstraction different from encapsulation?") → application ("show me in this code") → design ("now model a parking lot"). Our technical interview rubric breakdown covers how those later stages are scored; the sections below cover the distinctions in the order interviewers raise them.
Encapsulation vs abstraction: the distinction that fails the most candidates
Asked at HBO — Implement Director awards with Python OOP A data science round at HBO gave the candidate a small hierarchy where
DirectorextendsCastand asked for anadd_award(name, year)method that appends an award pair only if it is not already present. The catch: the implementation had to use@property,@classmethod, and@staticmethodappropriately, avoid mutable default arguments, and never leak the internal awards list to callers.
This question is a pure encapsulation probe wearing a Python costume. Both pitfalls it names (mutable defaults, leaked internal state) are ways candidates break encapsulation without noticing:
class Director(Cast):
def __init__(self, name: str, awards: list | None = None):
super().__init__(name)
# Copy the input. `awards: list = []` would share ONE list
# across every Director created without the argument.
self._awards: list[tuple[str, int]] = list(awards) if awards else []
@property
def awards(self) -> tuple[tuple[str, int], ...]:
# Return an immutable view. Returning self._awards directly
# lets callers do director.awards.append(...) and corrupt state
# without ever calling add_award.
return tuple(self._awards)
@classmethod
def from_dict(cls, d: dict) -> "Director":
# Alternate constructor. Uses cls, not Director, so a
# subclass calling from_dict gets an instance of itself.
return cls(d["name"], d.get("awards"))
@staticmethod
def _is_valid_year(year: int) -> bool:
# Needs neither self nor cls: a pure check that simply
# belongs with the class.
return year > 0
def add_award(self, award_name: str, year: int) -> None:
if self._is_valid_year(year) and (award_name, year) not in self._awards:
self._awards.append((award_name, year))
The three decorators the prompt demands each have a distinct job: @property exposes a read-only view of state, @classmethod builds instances polymorphically, and @staticmethod houses logic that needs no instance at all. The invariant here is "no duplicate (name, year) pairs". Encapsulation is what makes that invariant enforceable: the only write path is add_award, which checks it. Hand out the raw list and the invariant is a suggestion.
Now the distinction the interviewer is fishing for. Encapsulation is about protecting state. It answers "who is allowed to change this, and through what door?" Abstraction is about hiding mechanism. It answers "what does a caller need to know to use this?" A PaymentProcessor interface with a single charge(amount) method is abstraction: callers do not know whether Stripe or a mock sits behind it. The private _retry_count field inside the Stripe implementation, mutable only through its own retry logic, is encapsulation.
The common mistake is answering both with "hiding things". Hiding data behind accessors is encapsulation. Hiding implementation behind an interface is abstraction. If you give the interviewer one example of each (the copied list above for encapsulation, the charge() contract for abstraction), you have cleared a bar most candidates miss.
A quick self-test: could you have encapsulation without abstraction? Yes: a concrete class with private fields and no interface. Abstraction without encapsulation? Also yes: an interface implemented by a class whose fields are all public. They are independent tools that happen to travel together.
Inheritance vs composition, and why interviewers want to hear "composition"
Asked at Uber — Design an object-oriented parking lot system Uber's version of the classic asked for a multi-level lot where each level holds spots sized small, medium, or large, and vehicles are motorcycles, cars, or buses. A vehicle may only park in a compatible spot, and a bus may need several adjacent large spots. The system had to support entry (find and reserve a spot or group of spots) and exit.
The parking lot is where inheritance and composition earn or lose you the round, because the requirements force both:

Motorcycle is-a Vehicle: the hierarchy is small, stable, and every subtype genuinely substitutes for the base. Inheritance fits. But a ParkingLot is not a kind of anything; it is built from levels, which are built from spots. That is composition, and the bus requirement shows why it matters: "reserve three adjacent large spots" is a property of how a level composes its spots, not of any class hierarchy. Candidates who try to model it with inheritance (a BusSpot extending LargeSpot, then a DoubleBusSpot...) dig themselves into a subclass for every combination.
So why do interviewers push "prefer composition over inheritance" so hard? Three named failure modes of inheritance:
- The fragile base class. Change a base method and every subclass that overrode or depended on it can break, including subclasses in code you do not own. Composition confines change behind the composed object's interface.
- Combinatorial subclassing. Vary two axes independently (spot size and pricing policy, say) and inheritance needs a class per combination. Composition needs one class holding two fields.
- Liskov violations. The textbook
Square extends Rectanglebreaks the moment someone sets width and height independently. If a subtype cannot honor every promise of its base, the is-a claim was false and inheritance was the wrong tool.
The senior move is to name when inheritance is right: the relationship is truly is-a, the base is stable, and substitution holds. The Vehicle hierarchy above qualifies. Say that unprompted. An answer of "composition always" is as junior as "inheritance always".
The strategy pattern is the standard composition escape hatch, and it comes up in this exact question: spot-assignment policy ("nearest to elevator" vs "fill lowest level first") should be a SpotAssignmentStrategy the lot has, not a SmartParkingLot subclass. We work through the full class model, pricing, and the concurrency race at the gates in our dedicated parking lot design guide.
Polymorphism: compile-time vs runtime, and the trap question inside it
Asked at Intercontinental Exchange — Explain Vector, Linked List, and Polymorphism A C++ round at Intercontinental Exchange paired a container question (
std::vectorvsstd::liston memory layout, cache locality, iterator invalidation) with a direct probe: what does polymorphism mean in C++, what separates compile-time from runtime polymorphism, and where do overloading, templates, inheritance, and virtual functions each fit?
The clean split, in any language that has both:
Compile-time polymorphism is resolved from the declared (static) types the compiler sees. Function overloading and C++ templates live here; Java generics belong to the same compile-time world, though as type checking that is erased before runtime rather than target selection. Either way there is no runtime dispatch cost, because the work happened before the program ran.
Runtime polymorphism is resolved from the actual object at the call site, typically through a vtable. Method overriding lives here. The whole point is that the caller does not know the concrete type.
The trap question interviewers build from this:
abstract class Shape {
abstract double area();
}
class Circle extends Shape {
private final double r;
Circle(double r) { this.r = r; }
@Override double area() { return Math.PI * r * r; }
}
class Demo {
public static void main(String[] args) {
Shape s = new Circle(2.0);
System.out.println(s.area()); // 12.566... Circle.area() runs
}
}
"The variable is declared Shape: which area() runs, and when was that decided?" The answer: Circle.area(), decided at run time by looking at the object s actually points to. Candidates who say "the compiler picks it because s is a Shape" have confused the two mechanisms, and the interviewer now knows it. Follow-ups worth having ready: static, private, and final methods in Java are not dispatched dynamically (there is nothing to override), and in C++ dispatch is only dynamic if the method is declared virtual.
Python collapses the distinction, and interviewers who ask about OOP in Python probe exactly that edge:
class Exporter:
def export(self, data): ...
def export(self, data, fmt): ... # No overloading: this REPLACES
# the first def. Python keeps only
# the last binding of the name.
exporter = Exporter()
exporter.export({"rows": []}) # TypeError: missing 'fmt' --
# the 1-arg version is gone
Everything in Python is dynamically dispatched, and the contract is duck typing: any object with an export method works, no shared base class required. That is runtime polymorphism without inheritance, and naming it that way in an interview signals you understand the mechanism rather than the Java syntax.
One more senior signal: know when runtime polymorphism is the wrong choice. Virtual dispatch is an indirect call that the compiler usually cannot inline through (unless it can devirtualize the call, as with final classes, profile-guided optimization, or whole-program LTO), and losing inlining also blocks the optimizations inlining enables. That is why C++ code on hot paths (the ICE question is from an exchange, not a coincidence) often prefers templates, meaning compile-time polymorphism, over virtual calls. Saying "I'd take the vtable hit here because this is not a hot loop, but on a per-tick path I'd use templates" is the kind of trade-off sentence that separates senior answers from recitation.
Interface vs abstract class: it is a question about state
Asked at Duolingo — Design an Animal Chess OOP simulation Duolingo asked candidates to build a simplified Animal Chess (Dou Shou Qi) engine: eight piece types from Elephant down to Rat, each owning its movement and capture rules; a 9x7 board with rivers, traps, and dens; move validation, captures, turns, and win detection, all behind a small API.
Eight piece classes force the choice immediately. Every piece has state every other piece also has (rank, owner, current position) and behavior that varies: the Rat swims the river, the Lion and the Tiger jump it, everyone else walks around. Shared state plus a shared algorithm skeleton is the textbook case for an abstract class:
abstract class Piece {
protected final int rank;
protected final Player owner;
protected Cell position;
// Blank finals MUST be assigned in a constructor, or this
// class will not compile. The constructor is also where the
// invariants get established.
protected Piece(int rank, Player owner, Cell position) {
this.rank = rank;
this.owner = owner;
this.position = position;
}
// Template method: validation shared, movement rules per-piece.
final boolean tryMove(Cell target, Board board) {
if (!board.inBounds(target)) return false;
if (!canMoveTo(target, board)) return false; // subclass hook
return board.resolveCapture(this, target);
}
protected abstract boolean canMoveTo(Cell target, Board board);
}
That constructor is the point, not boilerplate: it is the one place the final fields can be assigned, which makes it the natural site to establish invariants — exactly the thing an interface cannot give you. An interface, by contrast, carries a contract and no instance state. If the game later needs "anything that can be rendered" — pieces, cells, the board itself — a Renderable interface is right, because those types share no fields and no ancestry.
| Interface | Abstract class | |
|---|---|---|
| Instance state | None (constants; default methods cannot touch fields) | Fields, constructors, invariants |
| How many can a class take? | Many (implements A, B, C) | One (Java/C# single inheritance) |
| Relationship it models | "Can do" across unrelated types | "Is a" within one family |
| Evolving it later | Adding a method needs a default body or it breaks every implementer | Can add a concrete method freely |
| Reach for it when | Unrelated types share a capability | Related types share state + partial implementation |
Two answers to retire. "Interfaces can't have method bodies" has been wrong in Java since default methods arrived in Java 8, and wrong in Python forever (an ABC can mix abstract and concrete methods however it likes). And "use an abstract class when you want code reuse" is only half true: the honest version is "when the reused code needs shared state". If there is no state, an interface with default methods keeps your one shot at a superclass free.
The Duolingo question also shows the two composing: Piece is an abstract class, while the board's cells might implement a TerrainRule interface for river/trap/den behavior. Real designs use both; interviews reward saying which one is doing which job.
The SOLID follow-ups that come after the definitions
Asked at SoFi — Explain GC, singleton, and OOP principles A SoFi Java round bundled three fundamentals: how generational garbage collection works at a high level, what the Singleton pattern is and how to implement it safely (thread safety, lazy vs eager, and why it hurts testing), and which SOLID principles the candidate actually uses in their own designs and how.
Note the phrasing of the third part: not "list SOLID" but "which ones do you use, and how do they change your code". That is how SOLID appears in practice, as a follow-up lens on something you already said or wrote. Probe-and-response for each:
- Single Responsibility — probe: "your
OrderServiceparses input, applies discounts, and writes to the DB; what would you change?" Response: one reason to change per class; split parsing and persistence out so a schema change cannot break discount logic. - Open/Closed — probe: "a new vehicle type arrives; what has to change?" In the Uber parking lot, adding
ElectricCarshould mean a new class and a new spot-compatibility entry, not edits to the entry-gate logic. If you find aswitchon vehicle type, that is the violation. - Liskov Substitution — probe: the
Square extends Rectanglequestion, or "can yourReadOnlyRepositorysubclass throw onsave()?" ThrowingUnsupportedOperationExceptionfrom an inherited method is the smell to name. - Interface Segregation — probe: "why not one big
Repositoryinterface?" Because a class that only reads should not be forced to stubdelete(). Small, capability-shaped interfaces; this connects straight back to the interface-vs-abstract-class section. - Dependency Inversion — probe: "how would you test this without a real database?" Depend on an abstraction the test can substitute. This is also the correct exit from the Singleton discussion, which is why SoFi paired them.
On the Singleton itself: know one safe Java implementation cold (the enum singleton or the static holder idiom, both of which get thread safety from class loading rather than from synchronized) and lead with the cost rather than the mechanics. A Singleton is a global reached through a static method: every caller hard-wires the dependency, nothing can substitute a fake in tests, and state leaks between test cases. "I'd implement it with the holder idiom, but at SoFi's scale I'd rather inject one shared instance through the constructor" answers both halves of the question in two sentences.
The mistake to avoid in any SOLID answer is reciting the acronym as five definitions. Interviewers hear that weekly. Anchor at least one principle to code you wrote earlier in the same interview ("my tryMove template method is open/closed with respect to new pieces") and the answer stops sounding memorized.
From explaining concepts to designing with them
Asked at Apple — Object-Oriented Design: Hotel Room Booking System Apple asked for the object model and core service of a hotel booking system:
checkAvailability(roomId, startDate, endDate)and a booking operation over a date range, with a clean class design and working logic. Once the model stood, the interview escalated to what happens when many booking requests for the same room arrive at once.
This is where the concept ladder tops out. Every pillar you just prepared shows up under a different name: Room and Booking are your abstractions; the invariant "no two confirmed bookings overlap for one room" is enforced by encapsulating booking state behind a book() method that checks conflicts; room types are a small inheritance question; pricing and cancellation policies are composition. Then the concurrency follow-up tests whether your encapsulation survives two threads — the same invariant, now needing a lock or a database constraint rather than a private field.
If your loop includes rounds like this, the preparation order that works is concepts first (this page), then the Object-Oriented Design interview guide for the step-by-step method, then the Low-Level Design guide for full worked designs. For distributed-scale follow-ups, the system design prep guide picks up where OOD stops.
Video: OOPS Interview Questions and Answers | Object Oriented Programming Interview Questions C#