The four pillars
Every OOP interview opens here. Give the definition in one line, then immediately say what it buys you - that second half is what distinguishes a real answer.
What is encapsulation?
Bundling data with the methods that operate on it, and restricting direct access to that data - typically private fields with public methods. What it buys you is control: you can validate on the way in, change the internal representation without breaking callers, and guarantee the object is never in an invalid state. 'Data hiding' is the mechanism; invariants are the point.
What is abstraction, and how is it different from encapsulation?
Abstraction is exposing what an object does while hiding how it does it - the shape of the interface. Encapsulation is protecting the internal state that implements it. Abstraction is about design (what the caller should need to know); encapsulation is about enforcement (what the caller is prevented from touching). A car's brake pedal is abstraction; the sealed brake system is encapsulation.
What is inheritance, and when should you avoid it?
Inheritance lets a class acquire the fields and behaviour of a parent, expressing an 'is-a' relationship. Avoid it when the relationship is really 'has-a' or 'uses-a', because it welds the subclass to the parent's implementation: changes to the parent ripple down, and deep hierarchies become impossible to reason about. If you are inheriting only to reuse a method, use composition instead.
What is polymorphism?
The same interface behaving differently depending on the underlying type - so a caller can hold a Shape reference and call draw() without knowing whether it is a Circle or a Square. Compile-time polymorphism is method overloading; run-time polymorphism is method overriding through dynamic dispatch. The practical value is that you can add a new type without editing any of the code that consumes it.
Practice this now
Overloading vs overriding
The most-asked comparison in the entire topic, and the one candidates most often garble. Learn the table, then be ready to explain why the resolution time differs.
| Aspect | Overloading | Overriding |
|---|---|---|
| Signature | Same name, different parameters | Same name, same parameters |
| Where | Usually within one class | Across a parent and child class |
| Resolved | At compile time (static binding) | At run time (dynamic dispatch) |
| Return type | May differ freely | Must match or be a covariant subtype |
| Also called | Compile-time polymorphism | Run-time polymorphism |
class Animal {
void speak() { System.out.println("..."); }
static void kind() { System.out.println("Animal"); }
}
class Dog extends Animal {
@Override
void speak() { System.out.println("Woof"); } // overrides
static void kind() { System.out.println("Dog"); } // hides, not overrides
}
Animal a = new Dog();
a.speak(); // "Woof" - resolved at run time from the object
Animal.kind(); // "Animal" - resolved at compile time from the typeWhy is overriding resolved at run time?
Because the compiler only knows the declared (reference) type, not the actual object. The decision must wait until an object exists, so the runtime looks up the method on the object's real class - via a virtual table in C++ or dynamic dispatch on the JVM. Overloading, by contrast, is fully determined by the argument types visible at compile time.
Can you override a static method?
No. Static methods belong to the class, not to instances, so there is no object to dispatch on. Declaring a static method with the same signature in a subclass hides the parent's method rather than overriding it, and which one runs depends on the reference type - a classic trick question.
Can a constructor be overloaded? Can it be overridden?
Overloaded yes - multiple constructors with different parameter lists are normal. Overridden no, because constructors are not inherited; a subclass constructor calls the parent's constructor rather than replacing it.
Practice this now
Related reading
Abstract class vs interface
The second-most-asked comparison. The honest modern answer notes that the technical gap has narrowed - interfaces can carry default method bodies in current Java, and C++ has always used abstract classes for both - so the real distinction is one of intent.
| Aspect | Abstract class | Interface |
|---|---|---|
| Expresses | A partially built 'is-a' type | A capability or contract |
| State | Can hold fields and constructors | No instance state |
| Inheritance | One per class (in Java, C#) | A class can implement many |
| Use when | Subclasses share code and identity | Unrelated classes share a capability |
When would you choose an interface over an abstract class?
When the thing you are describing is a capability rather than an identity, and when unrelated classes need it. Comparable, Serializable and Closeable are capabilities - a Bank Account and a Chess Move have nothing in common except that both can be compared. Use an abstract class when subclasses genuinely share both identity and implementation, such as an AbstractHttpHandler with common request plumbing.
If interfaces can have default methods, why keep abstract classes?
Because interfaces still cannot hold instance state or constructors, and a class can only extend one abstract class. Abstract classes remain the tool for shared mutable state and constructor logic; default methods exist mainly to let an interface evolve without breaking existing implementers.
What is the diamond problem, and how do languages solve it?
It arises when a class inherits the same member along two paths, leaving the compiler unable to choose. C++ allows multiple inheritance and solves it with virtual inheritance and explicit qualification. Java avoids it by allowing only single class inheritance, and where two interfaces supply conflicting default methods, the implementing class is forced to override and disambiguate.
Practice this now
Composition over inheritance
This is the question that separates candidates who have maintained code from candidates who have only written it. Have a concrete example ready.
What does 'prefer composition over inheritance' mean?
Build behaviour by holding other objects and delegating to them rather than by extending a base class. A Car should have an Engine, not extend Engine. Composition keeps coupling loose, lets you swap the collaborator at run time, and avoids inheriting a parent's entire surface area when you only wanted one method.
Give an example where inheritance is the wrong tool.
A Stack that extends ArrayList. It inherits every list operation, so callers can insert at an arbitrary index and violate the stack's invariant - the class can no longer guarantee last-in-first-out. Holding a private list and exposing only push, pop and peek fixes it. This is the Liskov substitution principle in practice: a subclass must be usable wherever the parent is, without surprising the caller.
What is the difference between association, aggregation and composition?
Association is any relationship between two objects. Aggregation is a 'has-a' where the part can outlive the whole - a Department has Employees, who still exist if the department closes. Composition is a stronger 'owns-a' where the part's lifetime is bound to the whole - a House owns its Rooms, which are meaningless without it.
Practice this now
SOLID principles
Do not recite the acronym. For each principle, give the one-line rule and the smell that tells you it is being violated - that is what shows you have applied it.
- ✓Single Responsibility - a class should have one reason to change. Smell: a class that touches the database, formats output and sends email.
- ✓Open/Closed - open for extension, closed for modification. Smell: a growing if-else or switch on a type code every time a new case appears.
- ✓Liskov Substitution - a subclass must work anywhere its parent does. Smell: a subclass that throws UnsupportedOperationException, or one whose override silently weakens a guarantee.
- ✓Interface Segregation - many small interfaces beat one large one. Smell: implementers forced to write empty method bodies they do not need.
- ✓Dependency Inversion - depend on abstractions, not concretions. Smell: a class that constructs its own database connection internally, making it impossible to test.
How does dependency inversion make code testable?
If a class accepts an abstraction through its constructor rather than instantiating a concrete dependency itself, a test can pass in a fake or a mock. The class under test no longer drags a database or a network call into your test suite - which is exactly why frameworks like Spring are built around dependency injection.
Which SOLID principle is violated most often in real code?
Single Responsibility, usually by accretion rather than by design - a class starts focused, then absorbs 'one more small thing' a dozen times. The tell is that unrelated changes keep touching the same file, and every change risks breaking something in a different area.
Practice this now
Language-specific follow-ups
Expect the conversation to drop into whichever language is on your resume. Prepare the version for yours.
Does Python support encapsulation without private keywords?
Yes, by convention plus name mangling. A single leading underscore signals 'internal, do not touch', and a double leading underscore triggers name mangling that makes accidental access from outside unlikely. Python's philosophy trades enforced privacy for trust in the caller, so encapsulation is a design discipline rather than a compiler guarantee.
Why does C++ need virtual destructors?
If you delete a derived object through a base pointer and the base destructor is not virtual, only the base destructor runs, so the derived part leaks. Any class intended as a polymorphic base needs a virtual destructor - a favourite interview trap.
How does Java achieve multiple inheritance of behaviour?
Through interfaces. A class can implement many interfaces, and since default methods were introduced an interface can supply behaviour as well as a contract. State is still single-inherited, which avoids the diamond problem for fields.
What is method hiding versus method overriding?
Overriding replaces an instance method and dispatches on the object's real type. Hiding happens with static methods or fields sharing a name, and resolves on the reference type at compile time. If a candidate says 'static methods are overridden', that is the mistake this question is fishing for.
Practice this now
How to answer OOP questions well
- ✓Definition, then purpose, then example - in that order, in about three sentences.
- ✓Reach for a concrete example from your own code. 'In my project, the payment handlers all implemented one interface, so adding UPI meant adding a class rather than editing a switch' beats any textbook definition.
- ✓Name the trade-off. Every OOP construct costs something - inheritance costs coupling, interfaces cost indirection, abstraction costs a layer to understand.
- ✓Do not over-claim. If you have not used a design pattern in real code, say you know it in principle rather than inventing experience.
- ✓Practise saying the answers out loud. OOP questions are conversational, and fluency is visibly different from recall.
Practice this now
