Understanding OOP Concepts
Understanding OOP Concepts Interview with follow-up questions
1. What is Object-Oriented Programming (OOP) and how is it implemented in C#?
Object-Oriented Programming (OOP) is a programming paradigm that models software as interacting objects — each combining state (data) and behavior (methods). C# is built around OOP and also supports functional and generic programming styles.
The four OOP pillars in C#:
1. Encapsulation — hide internal details, expose a controlled interface via access modifiers (private, public, internal, etc.) and properties.
2. Inheritance — a class inherits state and behavior from a base class using ::
public class Animal { public virtual void Speak() => Console.WriteLine("..."); }
public class Dog : Animal { public override void Speak() => Console.WriteLine("Woof!"); }
C# supports single class inheritance but multiple interface implementation.
3. Polymorphism — one interface, many behaviors. A List can hold Dog, Cat, etc., and calling Speak() on each invokes the right override at runtime (virtual dispatch).
4. Abstraction — define contracts without implementation details, via abstract classes and interface:
public interface IShape { double Area(); }
public abstract class Shape : IShape { public abstract double Area(); }
Modern C# extensions to OOP:
- Records (C# 9+) — immutable reference types with value-based equality; ideal for data objects.
- Default interface methods (C# 8+) — interfaces can provide a default implementation, enabling API evolution.
static abstractinterface members (C# 11) — enables generic algorithms that work across numeric types.- Pattern matching — often a cleaner alternative to type-hierarchy switch logic.
- Composition over inheritance — in modern .NET design (e.g., ASP.NET Core middleware), composition via dependency injection is preferred over deep inheritance chains.
Interviewers probe: When should you use inheritance vs. composition? — Inheritance for genuine IS-A relationships sharing implementation; composition (via interfaces and DI) for CAN-DO capabilities and flexibility. Over-inheritance leads to brittle hierarchies.
Follow-up 1
Can you give an example of a real-world scenario where OOP is beneficial?
One example of a real-world scenario where OOP is beneficial is in the development of a banking system. In this scenario, you can create classes such as 'Account', 'Customer', and 'Transaction'. The 'Account' class can have properties like 'accountNumber' and 'balance', and methods like 'deposit' and 'withdraw'. The 'Customer' class can have properties like 'name' and 'address', and the 'Transaction' class can have properties like 'amount' and 'date'. By using OOP, you can easily model the relationships between these classes and create objects that represent real-world entities, making it easier to manage and manipulate data.
Follow-up 2
What are the four main principles of OOP?
The four main principles of Object-Oriented Programming (OOP) are:
Encapsulation: It is the process of hiding the internal details of an object and exposing only the necessary information through methods and properties.
Inheritance: It allows a class to inherit properties and methods from another class, enabling code reuse and creating a hierarchy of classes.
Polymorphism: It allows objects of different classes to be treated as objects of a common base class, providing flexibility and extensibility in the code.
Abstraction: It is the process of simplifying complex systems by breaking them down into smaller, more manageable parts. It allows you to focus on the essential features of an object and ignore the irrelevant details.
Follow-up 3
How does OOP contribute to code reusability in C#?
Object-Oriented Programming (OOP) contributes to code reusability in C# through the use of inheritance and polymorphism. Inheritance allows a class to inherit properties and methods from another class, which means that you can create a base class with common functionality and then create derived classes that inherit and extend that functionality. This promotes code reuse and reduces duplication.
Polymorphism allows objects of different classes to be treated as objects of a common base class. This means that you can write code that operates on the base class, and it will automatically work with any derived classes that inherit from it. This promotes code reuse and makes it easier to extend and modify existing code without having to make changes in multiple places.
By using inheritance and polymorphism effectively, you can create a library of reusable classes and components, which can significantly reduce development time and effort.
2. What is inheritance in C# and how does it work?
Inheritance allows a class (the derived class) to reuse, extend, and specialize the members of another class (the base class). C# supports single class inheritance.
Syntax:
public class Animal
{
public string Name { get; set; } = string.Empty;
public virtual void Speak() => Console.WriteLine("...");
}
public class Dog : Animal
{
public override void Speak() => Console.WriteLine("Woof!");
public void Fetch() => Console.WriteLine("Fetching!");
}
// Usage
Dog d = new Dog { Name = "Rex" };
Animal a = d; // base reference to derived object
a.Speak(); // "Woof!" — runtime polymorphism
Key keywords:
| Keyword | Purpose |
|---|---|
virtual |
Marks a method as overridable in base class |
override |
Overrides a virtual/abstract method in derived class |
abstract |
Must be overridden; base class cannot be instantiated |
sealed |
Prevents further overriding (or inheritance of the class) |
new |
Hides a base member without overriding (breaks polymorphism — avoid) |
base |
Access base class constructor or members |
Constructor chaining:
public class Vehicle(string make) { }
public class Car(string make, int doors) : Vehicle(make) { }
sealed for performance: marking a class or method sealed allows the JIT to devirtualize calls, which can improve performance in hot paths.
Record inheritance (C# 9+):
record Animal(string Name);
record Dog(string Name, string Breed) : Animal(Name);
When NOT to use inheritance: prefer composition (interfaces + dependency injection) when you want flexibility. Deep inheritance hierarchies become brittle — the Liskov Substitution Principle (LSP) should always hold: derived class must be substitutable for the base without changing program correctness.
Interviewers probe: override vs new — override participates in virtual dispatch (polymorphism works correctly); new hides the base member (calling via base reference calls the base version). This is a classic C# gotcha.
Follow-up 1
Can you give an example of inheritance in C#?
Sure! Here's an example of inheritance in C#:
public class Animal
{
public void Eat()
{
Console.WriteLine("The animal is eating.");
}
}
public class Dog : Animal
{
public void Bark()
{
Console.WriteLine("The dog is barking.");
}
}
// Usage
Dog dog = new Dog();
dog.Eat(); // Output: The animal is eating.
dog.Bark(); // Output: The dog is barking.
Follow-up 2
What is multiple inheritance and does C# support it?
Multiple inheritance is a feature in some programming languages that allows a class to inherit from multiple base classes. However, C# does not support multiple inheritance of classes. This means that a class in C# can only inherit from a single base class. This design decision was made to avoid the complexities and ambiguities that can arise from multiple inheritance. However, C# does support multiple inheritance of interfaces, which allows a class to implement multiple interfaces.
Follow-up 3
What is the difference between inheritance and composition?
Inheritance and composition are two different ways to achieve code reuse in object-oriented programming:
Inheritance: Inheritance is an 'is-a' relationship, where a class inherits properties and methods from a base class. It allows for code reuse by promoting code organization and hierarchy. Inheritance is achieved using the
:symbol in C#.Composition: Composition is a 'has-a' relationship, where a class contains an instance of another class as a member. It allows for code reuse by combining multiple classes to create more complex objects. Composition is achieved by creating an instance of a class within another class.
In general, inheritance is used when there is an 'is-a' relationship between classes, while composition is used when there is a 'has-a' relationship between classes.
3. Can you explain the concept of polymorphism in C#?
Polymorphism (from Greek: "many forms") allows a single interface or type to represent different underlying implementations. C# supports two main forms:
Compile-time (static) polymorphism — method overloading:
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
// Resolved by the compiler based on argument types
Runtime (dynamic) polymorphism — virtual dispatch:
public abstract class Shape
{
public abstract double Area();
}
public class Circle(double radius) : Shape
{
public override double Area() => Math.PI * radius * radius;
}
public class Rectangle(double w, double h) : Shape
{
public override double Area() => w * h;
}
// Polymorphic usage
List shapes = [new Circle(5), new Rectangle(4, 6)];
foreach (var shape in shapes)
Console.WriteLine(shape.Area()); // calls the correct override at runtime
Interface polymorphism (preferred in modern C#):
public interface ILogger { void Log(string message); }
// Any class implementing ILogger is interchangeable at the call site
void Write(ILogger logger, string message) => logger.Log(message);
Pattern matching as modern polymorphism alternative:
double GetArea(Shape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
_ => throw new ArgumentException("Unknown shape")
};
Covariance and contravariance: generic interfaces can be covariant (IEnumerable — IEnumerable assignable to IEnumerable) or contravariant (Action — Action assignable to Action).
Interviewers probe: override vs new (method hiding) — with new, the method called depends on the declared type of the variable, breaking polymorphism. With override, the runtime type always wins.
Follow-up 1
What is the difference between static and dynamic polymorphism?
Static polymorphism, also known as compile-time polymorphism, is achieved through method overloading and function templates. It allows you to have multiple methods with the same name but different parameters. The appropriate method is selected at compile-time based on the arguments passed to it.
Dynamic polymorphism, also known as runtime polymorphism, is achieved through method overriding. It allows you to have a base class with a virtual method, which can be overridden by derived classes. The appropriate method is selected at runtime based on the actual type of the object.
Follow-up 2
How does polymorphism contribute to flexibility in C#?
Polymorphism contributes to flexibility in C# by allowing you to write code that can work with objects of different classes, as long as they inherit from the same base class or implement the same interface. This means that you can write code that is more generic and reusable, as it can operate on a variety of different objects without needing to know their specific types. This makes it easier to extend and modify your code in the future, as you can simply create new classes that inherit from the same base class or implement the same interface, and your existing code will still work with these new objects.
Follow-up 3
Can you give an example of polymorphism in C#?
Sure! Here's an example of polymorphism in C#:
public class Shape
{
public virtual void Draw()
{
Console.WriteLine("Drawing a shape...");
}
}
public class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle...");
}
}
public class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a rectangle...");
}
}
public class Program
{
public static void Main()
{
Shape shape1 = new Circle();
Shape shape2 = new Rectangle();
shape1.Draw(); // Output: Drawing a circle...
shape2.Draw(); // Output: Drawing a rectangle...
}
}
In this example, the Shape class is the base class, and the Circle and Rectangle classes are derived classes. The Draw method is marked as virtual in the Shape class, and it is overridden in the Circle and Rectangle classes. When we create objects of type Circle and Rectangle and assign them to variables of type Shape, we can call the Draw method on these variables, and the appropriate implementation of the method is called based on the actual type of the object.
4. What is encapsulation in C# and why is it important?
Encapsulation is the practice of bundling data and the operations on that data within a single unit (a class), while controlling what is visible to the outside.
Access modifiers — the mechanism:
| Modifier | Accessible from |
|---|---|
public |
Anywhere |
private |
The class itself only (default for members) |
protected |
Class + derived classes |
internal |
Same assembly |
protected internal |
Same assembly OR derived classes |
private protected |
Same assembly AND derived classes |
Why properties over public fields:
// BAD — direct field access
public decimal Balance;
// GOOD — controlled access via property
private decimal _balance;
public decimal Balance
{
get => _balance;
private set // only settable internally
{
if (value < 0) throw new ArgumentException("Balance cannot be negative");
_balance = value;
}
}
Properties let you add validation, lazy initialization, change notification, or computed values later without breaking callers. A public field cannot be changed to a property without a recompile of all callers (breaking change in .NET binary compatibility).
Modern encapsulation patterns:
init-only setters (C# 9+): settable in constructor/object initializer, then immutable — great for DTOs.requiredproperties (C# 11): compiler enforces they are set in object initializer.readonlyfields: assigned once (in declaration or constructor), never changed.internalwithInternalsVisibleTo: expose internals to test assemblies only.recordtypes: generate private backing for positional properties automatically.
Interviewers probe: what is the principle of least privilege in encapsulation? Start with private, open up only as needed. Also: explain why public fields are problematic in library design (binary compatibility, no binding support, no validation).
Follow-up 1
How does encapsulation contribute to data security in C#?
Encapsulation contributes to data security in C# by preventing direct access to an object's data from outside the class. By encapsulating the data and providing access through methods and properties, we can enforce validation and restrictions on how the data is accessed and modified. This helps in preventing unauthorized access and ensures that the data remains in a consistent state.
Follow-up 2
Can you give an example of encapsulation in C#?
Sure! Here's an example of encapsulation in C#:
public class BankAccount
{
private decimal balance;
public void Deposit(decimal amount)
{
// Perform validation and update the balance
}
public decimal GetBalance()
{
// Return the current balance
}
}
In this example, the balance field is encapsulated and can only be accessed and modified through the Deposit and GetBalance methods. This ensures that the balance is always updated and retrieved in a controlled manner.
Follow-up 3
What is the difference between encapsulation and abstraction?
Encapsulation and abstraction are two important concepts in object-oriented programming, but they serve different purposes.
Encapsulation is the process of hiding the internal details of an object and providing a public interface to interact with it. It focuses on data hiding and access control.
Abstraction, on the other hand, is the process of simplifying complex systems by breaking them down into smaller, more manageable parts. It focuses on defining the essential characteristics and behavior of an object without specifying the implementation details.
In summary, encapsulation is about data hiding and access control, while abstraction is about simplifying complex systems.
5. Can you explain the concept of abstraction in C#?
Abstraction means hiding the complexity of a system behind a simplified, relevant interface. You expose what something does, not how it does it.
Two C# mechanisms:
1. Abstract classes — define partial implementations that derived classes complete:
public abstract class DataExporter
{
// Template method pattern
public void Export(IEnumerable records)
{
var formatted = FormatRecords(records); // concrete step
WriteOutput(formatted); // abstract step
}
protected abstract void WriteOutput(string data);
private string FormatRecords(IEnumerable records) =>
string.Join("\n", records.Select(r => r.ToString()));
}
public class CsvExporter : DataExporter
{
protected override void WriteOutput(string data) =>
File.WriteAllText("output.csv", data);
}
2. Interfaces — pure contracts with no required state (can have default implementations in C# 8+):
public interface IPaymentProcessor
{
Task ProcessAsync(decimal amount, CancellationToken ct = default);
}
Choosing between them:
| Use abstract class when... | Use interface when... |
|---|---|
| Sharing code/state among related types | Defining a capability any type can implement |
| IS-A relationship | CAN-DO relationship |
| Single inheritance is acceptable | Multiple implementation needed |
| Need protected/constructor members | Need to work with unrelated types |
Modern abstraction features:
- C# 8 default interface methods: add new interface members without breaking existing implementors — enables API evolution.
- C# 11
static abstractmembers:interface INumber { static abstract T operator+(T a, T b); }— allows generic mathematical code across all numeric types.
Interviewers probe: what is the Dependency Inversion Principle? — High-level modules should depend on abstractions (interfaces), not concrete implementations. This is the foundation of dependency injection in modern .NET.
Follow-up 1
What is the difference between abstraction and interface in C#?
In C#, abstraction can be achieved through both abstract classes and interfaces. The main difference between abstraction and interface is that an abstract class can have both abstract and non-abstract methods, while an interface can only have method signatures and properties. Another difference is that a class can inherit from only one abstract class, but it can implement multiple interfaces. Abstract classes are used when we want to provide a common base implementation for a group of related classes, while interfaces are used when we want to define a contract that a class must adhere to.
Follow-up 2
How does abstraction contribute to simplicity in C#?
Abstraction contributes to simplicity in C# by hiding the unnecessary details and complexity of a system. It allows us to focus on the essential features and behavior of an object or a system, without getting overwhelmed by the implementation details. By providing a simplified interface, abstraction makes it easier to understand and work with complex systems. It also promotes code reusability and modularity, as abstract classes and interfaces can be used as building blocks for creating new classes. Overall, abstraction helps in creating more maintainable and scalable code.
Follow-up 3
Can you give an example of abstraction in C#?
Sure! Let's consider a simple example of abstraction in C#. Suppose we have a base class called 'Shape' which has an abstract method called 'CalculateArea'. This method is declared without an implementation in the 'Shape' class, and it must be implemented by any derived class. We can then create different derived classes such as 'Circle' and 'Rectangle' which inherit from the 'Shape' class. Each derived class will provide its own implementation of the 'CalculateArea' method based on its specific shape. By using abstraction, we can treat all shapes as 'Shape' objects and call the 'CalculateArea' method without worrying about the specific implementation details of each shape.
Live mock interview
Mock interview: Understanding OOP Concepts
- Read your scene and goals
- Talk it out; goals tick off live
- Get a score and stronger lines
Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.