OOP interviews test whether you understand the reasoning behind the four pillars, not just their definitions — why encapsulation matters, what actually breaks the Liskov Substitution Principle, why multiple inheritance causes the diamond problem. This covers all 52 questions, with code (mostly C#, since that's this site's home turf, with cross-language notes where it matters), a diagram, and a mock test at the end.
OOP Fundamentals
Q1. What is Object-Oriented Programming (OOP)?
OOP is a programming paradigm that models software around objects — bundles of state (data) and behavior (methods) — rather than a sequence of procedures operating on separate data. It's built on four pillars: encapsulation, inheritance, polymorphism, and abstraction, each addressing a different way of managing complexity as a codebase grows.
Q2. What is the difference between procedural and Object-Oriented programming?
| Procedural | Object-Oriented | |
|---|---|---|
| Organization | Functions operating on separate data structures | Objects bundling data and the behavior that operates on it |
| State | Often global or passed explicitly between functions | Encapsulated within objects, accessed through methods |
| Reuse | Function libraries | Inheritance, composition, polymorphism |
| Example languages | C, Pascal | Java, C#, Python, C++ |
Q3. What is encapsulation?
public class BankAccount
{
private decimal _balance; // hidden — no direct outside access
public decimal Balance => _balance;
public void Deposit(decimal amount)
{
if (amount <= 0) throw new ArgumentException("Must be positive");
_balance += amount;
}
}Encapsulation bundles an object's data with the methods that operate on it, and hides that data behind a controlled interface — _balance can't be set to a negative number from outside because there's no way to touch it except through Deposit(), which enforces the invariant. This is what keeps an object's internal state always valid, instead of trusting every caller everywhere to maintain it correctly themselves.
Q4. What is polymorphism? Explain overriding and overloading.
| Overriding | Overloading | |
|---|---|---|
| What varies | The implementation of the same method signature, in a subclass | Multiple methods with the same name, different parameters |
| Resolved at | Runtime (based on the actual object's type) | Compile time (based on the arguments' types) |
| Requires | virtual/override keywords (or an interface implementation) | Just different parameter lists on the same class |
public class Shape { public virtual double Area() => 0; }
public class Circle : Shape
{
public double Radius;
public override double Area() => Math.PI * Radius * Radius; // overriding
}
public class Calculator
{
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b; // overloading
}Polymorphism means the same method call behaves differently depending on the actual object it's invoked on ("many forms") — calling shape.Area() through a Shape reference runs Circle's implementation if that's the real underlying object, resolved at runtime. Overloading is a different, compile-time mechanism entirely — picking which of several same-named methods to call based on argument types.
Q5. What is inheritance? Name some types of inheritance.
| Inheritance type | Shape |
|---|---|
| Single | One class inherits from one base class |
| Multiple | One class inherits from more than one base class (not supported by C#/Java for classes) |
| Multilevel | A chain — C inherits from B, B inherits from A |
| Hierarchical | Multiple classes inherit from the same base class |
Inheritance lets a class (the subclass) acquire the members of another class (the base class), modeling an "is-a" relationship — a Dog is an Animal. C# and Java deliberately don't allow multiple inheritance of classes (only of interfaces) specifically to avoid the diamond problem covered later.
Q6. What is an abstraction? Name some abstraction techniques.
Abstraction hides implementation complexity behind a simpler interface, exposing only what a consumer actually needs to know. Techniques: abstract classes (partial implementation plus abstract members to be filled in), interfaces (a pure contract with no implementation), and encapsulation itself (hiding internal state behind methods) all serve abstraction in different ways.
Q7. What is a class in OOP?
A class is a blueprint — it defines the fields (state) and methods (behavior) that every object created from it will have, but a class itself holds no actual data until it's instantiated.
Q8. What is an object in OOP?
var fido = new Dog { Name = "Fido" }; // fido is an object — a concrete instance of the Dog classAn object is a concrete instance of a class — it has its own actual state (its own values for the class's fields) in memory, distinct from every other instance of the same class.
Q9. How do access specifiers work and what are they typically?
| Access modifier (C#) | Visible to |
|---|---|
| public | Anywhere |
| private | Only within the same class |
| protected | The class and its subclasses |
| internal | Anywhere within the same assembly |
| protected internal | Subclasses, OR anywhere in the same assembly |
Access specifiers control which other code can see/use a given member — the enforcement mechanism that actually makes encapsulation possible, rather than encapsulation being just a naming convention developers have to remember to respect.
Q10. Name some ways to overload a method.
public void Print(int value) { ... }
public void Print(string value) { ... } // different parameter type
public void Print(int value, bool bold) { ... } // different parameter count
public void Print(double value) { ... } // different parameter type againOverloads must differ by parameter count and/or parameter types (or order) — return type alone isn't enough to distinguish two overloads, since the compiler resolves which overload to call based on the arguments at the call site, not the expected return value.
Q11. What is cohesion in OOP?
Cohesion measures how closely related a class's responsibilities are — a highly cohesive class does one well-defined thing (an EmailValidator only validates emails), while a low-cohesion class does several unrelated things bundled together for no good reason. High cohesion is what you want; it's the same idea the Single Responsibility Principle is built around.
Q12. What is coupling in OOP?
Coupling measures how much one class depends on the internal details of another — tight coupling means a change in one class is likely to break or require changes in another; loose coupling (achieved through interfaces and dependency injection) means classes can change independently as long as the shared contract stays stable.
Class Relationships and Design
Q13. What is a constructor and how is it used?
public class Point
{
public int X, Y;
public Point(int x, int y) { X = x; Y = y; } // constructor
}
var p = new Point(3, 4);A constructor is a special method that runs when an object is created, responsible for initializing its state to a valid starting point — it's how you guarantee an object never exists in an incomplete or invalid state, since the constructor is the one place required to run before anything else can use the object.
Q14. Describe the concept of destructor or finalizer in OOP.
A destructor/finalizer runs cleanup logic when an object is being reclaimed. In garbage-collected languages like C#, finalizers run non-deterministically whenever the GC gets to it (or possibly never, if the process exits first), which is exactly why C# favors the IDisposable pattern (a deterministic Dispose() you call explicitly, often via using) for releasing resources like file handles or database connections, rather than relying on a finalizer's timing.
Q15. Compare inheritance vs. mixin vs. composition.
| Inheritance | Mixin | Composition | |
|---|---|---|---|
| Relationship | "is-a" | "can-do" — adds capability, not identity | "has-a" |
| Coupling | Tightest — subclass depends on base's implementation details | Moderate — depends on the mixin's contract | Loosest — depends only on the composed object's interface |
| Flexibility | Fixed at compile time, one base class | Multiple mixins can be combined | Can be swapped/reconfigured at runtime |
"Favor composition over inheritance" is a common piece of OOP wisdom precisely because composition is the most flexible and loosely coupled of the three — a class holding a reference to a collaborator (composition) can swap that collaborator for a different implementation freely, while inheriting from a base class locks in a much tighter, compile-time-fixed relationship.
Q16. Explain the concept of an interface and how it differs from an abstract class.
| Interface | Abstract class | |
|---|---|---|
| Implementation | None (traditionally) — pure contract | Can provide default/shared implementation alongside abstract members |
| Multiple inheritance | A class can implement many interfaces | A class can inherit from only one abstract class |
| State | Can't hold instance fields | Can hold instance fields and constructors |
| Use when | Defining a capability/contract unrelated classes can share | Sharing common implementation among closely related classes |
Q17. Can a class have multiple parents in a single-inheritance system?
No — that's exactly what "single inheritance" means: a class can extend only one base class. It can, however, implement multiple interfaces, which is how C#/Java let a class take on multiple unrelated contracts without the ambiguity multiple class inheritance would introduce.
Q18. How would you design a class to prevent it from being subclassed?
public sealed class ImmutablePoint
{
public readonly int X, Y;
public ImmutablePoint(int x, int y) { X = x; Y = y; }
}Mark the class sealed (C#) or final (Java) — the compiler then rejects any attempt to derive from it. Useful for classes whose invariants (immutability, a fixed contract) would be undermined if a subclass could override behavior in unexpected ways.
Q19. Explain the 'is-a' vs 'has-a' relationship in OOP.
// is-a — inheritance
public class Car : Vehicle { }
// has-a — composition
public class Car
{
private Engine _engine; // a Car HAS an Engine, it isn't one
}"Is-a" (a Car is a Vehicle) is modeled with inheritance; "has-a" (a Car has an Engine) is modeled with composition — a common design mistake is reaching for inheritance when the real relationship is actually "has-a," producing an inheritance hierarchy that doesn't hold up.
Q20. Explain how aggregation relationship is represented in OOP.
public class Department
{
public List<Professor> Professors; // aggregation — Professors outlive the Department
}Aggregation is a weaker form of "has-a" than composition — the contained object's lifetime is independent of the container's (a Professor still exists if their Department is dissolved), unlike true composition where the parts genuinely can't outlive the whole (an Order's OrderLines don't exist without the Order).
Q21. What is method overriding, and what rules apply to it?
- The overriding method must have the same signature (name, parameters, return type) as the base method
- The base method must be marked virtual (or be an interface member) — you can't override a non-virtual method in C#
- Access level can't be more restrictive than the base method's
- The override keyword is required in C#, making the intent explicit rather than accidental
Q22. Describe the use of static methods and when they are appropriate.
public static class MathHelpers
{
public static int Square(int x) => x * x;
}A static method belongs to the class itself, not to any instance — appropriate for pure utility functions that don't need or use any instance state (Math.Sqrt being the canonical example). Overusing static methods for things that genuinely operate on object state is a common way to accidentally abandon OOP's benefits (testability, polymorphism) for no real gain.
Advanced OOP Concepts
Q23. What is multiple inheritance and what are some of its disadvantages?
Multiple inheritance lets a class inherit from more than one base class simultaneously (supported in C++/Python, not in C#/Java for classes). The main disadvantage is exactly the diamond problem below — ambiguity when two parent classes both define a member with the same name, and no clear rule for which one "wins" without extra language machinery to resolve it.
Q24. Can you explain the 'diamond problem' in multiple inheritance?
If class D inherits from both B and C, and both B and C inherit from A and override the same method, D doesn't have a clear, unambiguous way to know which version to use — the shape of the inheritance graph literally forms a diamond (A at top, D at bottom). Languages with multiple inheritance solve this with explicit resolution rules (C++'s virtual inheritance, Python's MRO/C3 linearization); C#/Java avoid the problem entirely by simply not allowing multiple class inheritance.
Q25. How does OOP languages support polymorphism under the hood?
Most commonly via a virtual method table (vtable) — each class with virtual methods gets a table of function pointers, and an object carries a pointer to its class's vtable. Calling shape.Area() looks up Area in the actual object's vtable at runtime, which is why the correct overridden version runs even when called through a base-class reference — this is literally how runtime ("dynamic") polymorphism is implemented at the machine level.
Q26. What are generics, and how can they be useful in OOP?
public class Box<T>
{
private T _item;
public void Put(T item) => _item = item;
public T Get() => _item;
}
var intBox = new Box<int>(); // type-safe, no casting needed
var stringBox = new Box<string>();Generics let a class or method work with any type while preserving compile-time type safety — Box<int> and Box<string> share exactly one implementation, with no runtime casting and no risk of putting the wrong type in by mistake, unlike a pre-generics design using object and manual casts.
Q27. Explain the concept of object composition and its benefits.
Composition builds complex behavior by combining simpler objects as fields, rather than inheriting from a base class — the benefits are loose coupling (the contained object is just referenced through its interface) and runtime flexibility (you can swap the composed object for a different implementation, something inheritance's compile-time-fixed relationship can't do).
Q28. What is Liskov Substitution Principle (LSP)? Provide some examples of violation and adherence.
// Violation: Square is-a Rectangle, but changing Width silently also changes Height —
// code that expects Rectangle's normal behavior breaks when given a Square instead.
public class Square : Rectangle
{
public override int Width
{
set { base.Width = value; base.Height = value; } // surprising side effect
}
}
// Adherence: don't force an is-a relationship that doesn't actually hold behaviorally —
// model Square and Rectangle as separate types implementing a common IShape instead.LSP says a subclass should be substitutable for its base class without breaking the caller's expectations — any code written to work with a Rectangle should keep working correctly if handed a Square instead. The classic Square/Rectangle example above violates it because Square subtly changes Rectangle's expected behavior (setting Width alone shouldn't also change Height), which is exactly the kind of "is-a" relationship that looks right conceptually but breaks down behaviorally.
Q29. What is the dependency inversion principle?
High-level modules shouldn't depend on low-level modules directly — both should depend on abstractions. An OrderService (high-level) should depend on an IEmailSender interface, not a concrete SmtpEmailSender (low-level) — this is the 'D' in SOLID, and it's the design principle Dependency Injection is the most common technique for actually achieving.
Q30. How can the open/closed principle guide object-oriented design?
// Closed for modification, open for extension — add a new discount without touching existing code:
public interface IDiscountStrategy { decimal Apply(decimal price); }
public class SeasonalDiscount : IDiscountStrategy { ... }
public class NewLoyaltyDiscount : IDiscountStrategy { ... } // added later, nothing else changesThe Open/Closed Principle says a class should be extendable with new behavior without modifying its existing, already-tested source code — achieved through abstraction (interfaces, virtual methods, the Strategy pattern) so new behavior plugs in as a new implementation rather than an edit to existing logic.
Q31. Describe how the Interface Segregation Principle affects system design.
// Violates ISP — a printer that can't scan is still forced to implement Scan()
public interface IMachine { void Print(); void Scan(); void Fax(); }
// Follows ISP — smaller, focused interfaces
public interface IPrinter { void Print(); }
public interface IScanner { void Scan(); }ISP says a class shouldn't be forced to implement methods it doesn't actually use — a fat interface with many unrelated members should be split into several smaller, focused ones, so an implementer only takes on the specific capability it genuinely needs.
Q32. What is a mixin, and how does it differs from traditional inheritance?
A mixin is a reusable chunk of behavior meant to be combined into a class without establishing a true "is-a" relationship — Python and Ruby support them natively via multiple inheritance/module inclusion; C# approximates the idea with default interface methods or extension methods. Unlike a base class, a mixin isn't meant to represent identity, just to inject a specific capability (comparability, serializability) into otherwise unrelated classes.
Practical Use and Patterns
Q33. How would you refactor a class that has too many responsibilities?
Identify the distinct responsibilities (validation, persistence, notification, formatting) and extract each into its own focused class, then have the original class coordinate between them (or remove it entirely if it was just an accidental grab-bag) — the practical application of the Single Responsibility Principle, guided by low cohesion as the actual signal something needs splitting.
Q34. Describe a singleton pattern and discuss its pros and cons.
public sealed class ConfigManager
{
private static readonly ConfigManager _instance = new();
public static ConfigManager Instance => _instance;
private ConfigManager() { }
}| Pros | Cons |
|---|---|
| Guarantees exactly one instance | Introduces a hidden global dependency, hurting testability |
| Global, convenient access point | Can hide coupling that would be more visible/explicit as an injected dependency |
| Useful for genuinely single-instance resources (a config, a connection pool) | Overused as a lazy substitute for proper dependency management |
Q35. What is a factory method, and when should it be used?
public abstract class ShapeFactory
{
public abstract IShape Create();
}
public class CircleFactory : ShapeFactory
{
public override IShape Create() => new Circle();
}The Factory Method pattern defines a method for creating an object without specifying its exact concrete class in the calling code — use it when the exact type to construct depends on a condition (configuration, runtime input) that the caller shouldn't need to know or decide directly.
Q36. Explain the builder pattern and where you might apply it.
var pizza = new PizzaBuilder()
.WithSize(Size.Large)
.AddTopping("Pepperoni")
.AddTopping("Mushrooms")
.Build();Builder constructs a complex object step by step through a fluent chain of calls, instead of one constructor with a dozen optional parameters — appropriate when an object has many optional configuration points and a single constructor call would be unreadable or need many overloads.
Q37. What is the prototype pattern, and how does it relate to OOP?
var original = new Enemy { Health = 100, Weapon = "Sword" };
var clone = original.Clone(); // a copy, independent of the original from that point onPrototype creates new objects by cloning an existing instance rather than constructing from scratch — useful when object creation is expensive, or when you want a new object that starts from a pre-configured template rather than default values.
Q38. When would you use the Adapter pattern?
public interface IModernLogger { void Log(string message); }
public class LegacyLoggerAdapter : IModernLogger
{
private readonly LegacyLogger _legacy;
public void Log(string message) => _legacy.WriteToLog(message); // translates the call
}Adapter wraps an existing class with an incompatible interface so it can be used where a specific different interface is expected — for integrating a legacy library or third-party API into code written against your own, cleaner abstraction, without modifying the legacy code itself.
Q39. Can you explain the use of the Decorator pattern?
public class LoggingRepository : IRepository
{
private readonly IRepository _inner;
public LoggingRepository(IRepository inner) => _inner = inner;
public Order GetById(int id)
{
Console.WriteLine($"Fetching order {id}");
return _inner.GetById(id); // delegates, then adds behavior around it
}
}Decorator wraps an object to add behavior (logging, caching, validation) around its existing methods, while implementing the same interface — so decorators can be stacked, and the caller can't tell (or need to care) whether it's talking to the real object or a chain of decorators around it.
Q40. Describe the Observer pattern and a scenario in which you might use it.
The Observer pattern
Subject (Publisher)
Holds a list of subscribed observers
State changes
Something happens the subject needs to announce
Notify()
The subject calls every subscribed observer
Observers react
Each observer independently handles the notification
Observer lets a subject notify a list of subscribed observers whenever its state changes, without the subject needing to know anything specific about who's listening or what they'll do — the classic real-world use is a UI element updating automatically when the underlying data model changes (data binding), or an event system where multiple unrelated parts of an application react to the same event.
Q41. What are the advantages of using the Command pattern?
public interface ICommand { void Execute(); void Undo(); }
public class AddTextCommand : ICommand
{
public void Execute() { /* add text */ }
public void Undo() { /* remove the text that was added */ }
}Command encapsulates a request/action as an object, which is what enables undo/redo (each command knows how to reverse itself), queuing/scheduling commands for later execution, and logging every action taken as a discrete, replayable object rather than an already-executed side effect.
Q42. How does the Strategy pattern provide flexibility in objects?
public interface ISortStrategy { void Sort(List<int> data); }
public class Sorter
{
private ISortStrategy _strategy;
public Sorter(ISortStrategy strategy) => _strategy = strategy;
public void Sort(List<int> data) => _strategy.Sort(data);
}
// Swap Sorter's algorithm at runtime by passing a different ISortStrategy —
// no change to Sorter's own code at all.Strategy defines a family of interchangeable algorithms behind a common interface, injected into the context that uses them — the algorithm can be swapped at runtime (or via configuration) without touching the class that uses it, which is the Open/Closed Principle applied directly to "which algorithm runs."
OOP Best Practices
Q43. What are some common OOP design anti-patterns?
- God Object — one class that knows/does far too much, a direct violation of Single Responsibility
- Anemic Domain Model — classes that are just data bags with no real behavior, pushing all logic into separate "service" classes
- Inheritance for code reuse alone, when the relationship isn't actually "is-a" (composition would fit better)
- Feature envy — a method that mostly operates on another object's data instead of its own, suggesting it belongs on that other class
Q44. How do you ensure that your objects are properly encapsulated?
- Keep fields private by default, exposing state only through methods/properties that enforce invariants
- Never expose a mutable internal collection directly — return a read-only view or a copy instead
- Validate inputs at the boundary (constructors, setters) so an object can never reach an invalid state
Q45. Name some techniques for reducing coupling between classes.
- Depend on interfaces/abstractions rather than concrete classes (Dependency Inversion)
- Use Dependency Injection so classes receive collaborators rather than constructing them directly
- Favor composition over inheritance to avoid tight coupling to a base class's internals
- Keep interfaces small and focused (Interface Segregation) so a class only depends on what it actually needs
Q46. How does immutability help in object-oriented design, and how can it be implemented?
public sealed class Money
{
public decimal Amount { get; }
public string Currency { get; }
public Money(decimal amount, string currency) { Amount = amount; Currency = currency; }
public Money Add(Money other) => new Money(Amount + other.Amount, Currency); // returns a NEW instance
}An immutable object can't change after construction, which eliminates an entire category of bugs — shared references unexpectedly mutating, thread-safety issues from concurrent writes — since there's simply nothing to mutate. Implement it with readonly/get-only properties set only in the constructor, and methods that return a new instance rather than mutating in place (as Add() does above).
Q47. What tools or techniques would you use to document an object-oriented design?
- UML class diagrams — showing classes, their relationships (inheritance, composition, aggregation), and multiplicities
- Sequence diagrams for how objects collaborate to fulfill one specific use case over time
- Inline documentation (XML doc comments in C#) capturing the why behind a non-obvious design decision, not just restating the code
Q48. How do you address circular dependencies in an OOP system?
Introduce an interface or event that one side depends on instead of the other concrete class directly, extract the shared behavior both classes need into a third class they both depend on, or restructure so the dependency only flows one direction — the same underlying fix whether the cycle shows up in a class design or in DI constructor injection specifically.
Q49. Explain how to apply unit testing to object-oriented code.
[Fact]
public void Deposit_IncreasesBalance()
{
var account = new BankAccount();
account.Deposit(100);
Assert.Equal(100, account.Balance);
}Test an object's public behavior/contract, not its private implementation details — construct it, exercise a public method, and assert on the observable outcome. Encapsulation and dependency injection are what make this practical: private state can't be poked directly (so tests can't accidentally couple to internals), and injected dependencies can be mocked to isolate the class under test.
Q50. What strategies can be used to safely refactor legacy object-oriented code?
- Write characterization tests first, capturing the existing (even if imperfect) behavior before changing anything
- Refactor in small, independently verifiable steps — extract a method, then a class, rather than one large rewrite
- Introduce an interface around a legacy dependency (the Adapter pattern) to isolate it before touching its internals
Q51. How can the principles of OOP help in achieving a modular and maintainable codebase?
Encapsulation limits the blast radius of a change to one class's internals; abstraction lets consumers depend on stable contracts instead of volatile implementation details; polymorphism lets new behavior be added by implementing an existing interface rather than modifying existing code; and low coupling/high cohesion (which the SOLID principles collectively push toward) keep each module's responsibilities focused enough to actually reason about in isolation.
Q52. How do you balance the use of OOP principles with performance considerations in a system design?
Abstraction layers (virtual method calls, interface indirection) carry a small real cost, and it genuinely matters in a tight, hot loop — but for the vast majority of code, that cost is irrelevant next to the maintainability OOP buys. The practical approach: default to clean OOP design everywhere, and only trade abstraction for raw performance in profiled, measured hot paths where it's actually been shown to matter — not preemptively across an entire codebase.
Test yourself
A quick mock test covering the ground above — the rapid-fire round that often opens an OOP interview before the deeper design discussion.
Question 1 of 8
Score: 0 / 0
1. What are the four pillars of Object-Oriented Programming?