The 4 Pillars of OOP in C# (Interview Guide)

1 min read

Your Interview Guide to the Four Pillars of OOP in C#

Object-Oriented Programming (OOP) is the core paradigm of C#. An interviewer will always check your understanding of its four main pillars. Being able to define them simply and provide a code example is essential.

Here is the simple, one-sentence definition for each:

  1. Encapsulation: The concept of bundling data (fields) and the methods that operate on that data into a single unit (a 'class'), and hiding the internal state from the outside world.
  2. Abstraction: Hiding complex implementation details and exposing only the necessary features to the user (e.g., an interface or abstract class).
  3. Inheritance: The mechanism that allows a new class (a 'child' or 'derived' class) to acquire the properties and methods of an existing class (a 'parent' or 'base' class).
  4. Polymorphism: The ability for an object to take on many forms. In practice, it means a parent class reference can be used to refer to a child class object, allowing the child to `override` and provide its own implementation of a method.

This cluster will provide a specific, code-based interview answer for each pillar.

OOP Pillar 1: Encapsulation Explained (`public` vs. `private`)

Interview Question: 'What is Encapsulation?'

Answer: 'Encapsulation is the principle of 'data hiding'. It means bundling the data (fields) and the methods that can change that data within a class, and protecting that data from the outside world using access modifiers like private. We then expose controlled access to that data using public methods or properties (getters/setters).'

The goal is to prevent other classes from 'reaching in' and putting your object into an invalid state.


'Before': No Encapsulation (BAD 👎)

Here, the balance field is public. Any other class can change it, even to an invalid value.

public class BankAccount {
  public decimal balance; // BAD! This is public.
}

public class Program {
  public void Main() {
    BankAccount account = new BankAccount();
    account.balance = -1000.00m; // PROBLEM! The account is now in an invalid state.
  }
}

'After': With Encapsulation (GOOD 👍)

Here, the _balance field is private. The only way to change it is through the Deposit or Withdraw methods. This protects our business logic.

public class BankAccount {
  // 1. The data is 'private' and hidden.
  private decimal _balance = 0;

  // 2. We expose a 'public' property for reading (a 'getter')
  public decimal Balance {
    get { return _balance; }
  }

  // 3. We expose 'public' methods to safely modify the data
  public void Deposit(decimal amount) {
    if (amount > 0) {
      _balance += amount;
    }
  }

  public bool Withdraw(decimal amount) {
    if (amount > 0 && _balance >= amount) {
      _balance -= amount;
      return true; // Success
    }
    return false; // Failure (e.g., insufficient funds)
  }
}

public class Program {
  public void Main() {
    BankAccount account = new BankAccount();
    // account._balance = -1000.00m; // COMPILE ERROR! Cannot access private field.

    // You must use the methods, which contain the safety checks.
    account.Deposit(100);
    account.Withdraw(50);
  }
}

OOP Pillar 2: Inheritance Explained (`base` and `derived`)

Interview Question: 'What is Inheritance?'

Answer: 'Inheritance is the mechanism for code reuse. It allows a new class, called the 'derived' or 'child' class, to inherit the non-private fields and methods of an existing 'base' or 'parent' class. This creates an 'is-a' relationship (e.g., a Dog 'is-a' Animal).'


Code Example: `base` and `derived` Classes

Here, we have a base class Animal that has common logic. The derived classes Dog and Cat inherit this logic so we don't have to rewrite it.

// 1. The 'base' or 'parent' class
public class Animal {
  public string Name { get; set; }

  public Animal(string name) {
    Name = name;
    Console.WriteLine("Animal constructor called.");
  }

  public void Eat() {
    Console.WriteLine($"{Name} is eating.");
  }
}

// 2. The 'derived' or 'child' class
// 'Dog' inherits from 'Animal' using the ':' syntax
public class Dog : Animal {
  // 3. We must call the base constructor using 'base()'
  public Dog(string name) : base(name) {
    Console.WriteLine("Dog constructor called.");
  }

  // This is a new method only for Dog
  public void Bark() {
    Console.WriteLine($"{Name} says woof!");
  }
}

public class Program {
  public void Main() {
    Dog myDog = new Dog("Fido");

    // 'myDog' has access to its own methods:
    myDog.Bark(); // Output: Fido says woof!

    // 'myDog' also has access to its parent's methods:
    myDog.Eat(); // Output: Fido is eating.
    Console.WriteLine(myDog.Name); // Output: Fido
  }
}
// Console output from 'new Dog("Fido")':
// Animal constructor called.
// Dog constructor called.

OOP Pillar 3: Polymorphism Explained (`virtual`, `override`, `new`)

Interview Question: 'What is Polymorphism?'

Answer: 'Polymorphism means 'many forms'. It's the ability for a 'derived' (child) class to provide its own, specific implementation of a method from its 'base' (parent) class. It allows us to treat a Dog or Cat object as just an Animal, and when we call the MakeSound() method, the correct version (bark or meow) is executed at runtime.'

This is achieved in C# using the virtual and override keywords.


Code Example: virtual and override

This is the classic example of true polymorphism.

public class Animal {
  // 1. Mark the base method as 'virtual'
  // This means 'derived classes are allowed to override me.'
  public virtual void MakeSound() {
    Console.WriteLine("Animal makes a generic sound.");
  }
}

public class Dog : Animal {
  // 2. 'override' the base method
  // This provides a new implementation.
  public override void MakeSound() {
    Console.WriteLine("Woof!");
  }
}

public class Cat : Animal {
  // 3. 'override' the base method
  public override void MakeSound() {
    Console.WriteLine("Meow!");
  }
}

public class Program {
  public void Main() {
    // 4. This is Polymorphism in action!
    // We create a list of 'Animal' (base type).
    List animals = new List();

    // 5. We can add 'Dog' and 'Cat' (derived types) to it.
    animals.Add(new Animal());
    animals.Add(new Dog());
    animals.Add(new Cat());

    foreach (Animal a in animals) {
      // 6. The correct method is called at runtime,
      // based on the actual object's type.
      a.MakeSound();
    }
  }
}
// --- CONSOLE OUTPUT ---
// Animal makes a generic sound.
// Woof!
// Meow!

Interview 'Gotcha': override vs. new

An interviewer might ask, 'What's the difference between override and new?'

  • override (Polymorphism): Replaces the base class method. As seen above, when you call it on the base Animal reference, the Dog's new method is called.
  • new (Method Hiding): Hides the base class method. If you call it on the base Animal reference, the base method is called. This is NOT polymorphism and is almost always a bug.
public class Wolf : Animal {
  // 'new' hides the base method. It does not override it.
  public new void MakeSound() {
    Console.WriteLine("Howl!");
  }
}

// In Main...
Animal myWolf = new Wolf();
myWolf.MakeSound(); // Output: 'Animal makes a generic sound.'

// The 'Howl' method is only called if you have a 'Wolf' reference:
Wolf realWolf = new Wolf();
realWolf.MakeSound(); // Output: 'Howl!'

💬