AlgoDaily

Object Oriented Programming Refresher

Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.

Why OOP Now

Your final interview cheat sheet

Now that we've covered Introduction to CI/CD in Azure, Azure Cloud Overview, Advanced C# Techniques, and Microservices Architecture, we're ready to revisit the design foundation underneath all of them: Object Oriented Programming.

For a senior C# backend role, OOP questions are rarely about textbook definitions only. Interviewers want to know if you can design code that is:

  • Easy to change without breaking production
  • Testable in CI/CD pipelines
  • Safe around business invariants
  • Clear at service boundaries
  • Practical in Azure-hosted, microservice-based systems

Think of this lesson as the final polish: how to explain OOP clearly, defend design tradeoffs, and connect principles to real backend work.

Why OOP Now

OOP Mental Model

Objects are behavior plus rules

A useful senior-level definition:

Object Oriented Programming organizes software around objects that own state, protect invariants, and expose behavior through clear contracts.

Avoid saying only: “OOP is classes and objects.” That sounds junior.

A stronger mental model:

Outside code
    |
    v
Public contract  ->  Behavior methods
                         |
                         v
                   Private state + invariants

The object should make invalid states difficult or impossible. This matters in backend systems because invalid state becomes bad data, bad messages, failed deployments, and production incidents.

In C#, OOP shows up through:

  • Encapsulation: protect state and invariants
  • Abstraction: expose only what callers need
  • Inheritance: share or specialize behavior carefully
  • Polymorphism: call behavior through a common contract

The senior move is knowing when each one helps — and when it adds unnecessary complexity.

OOP Mental Model

Encapsulation Check

A BankAccount class exposes a public Balance setter so any caller can directly assign a new balance. What is the main design problem?

Protect Invariants

Encapsulation is not hiding for hiding's sake

Encapsulation means the object controls how its state changes.

For senior C# interviews, connect this to domain safety:

  • Do not expose mutable state unless callers truly own it
  • Prefer methods that express business intent
  • Validate transitions at the boundary of the object
  • Keep invalid states unrepresentable where practical

In a microservice, this is especially important because each service owns its data. Once invalid data is persisted or emitted as an event, the problem spreads.

```csharp
using System;

public sealed class BankAccount
{
    public string AccountId { get; }
    public decimal Balance { get; private set; }

    public BankAccount(string accountId, decimal openingBalance)
    {
        if (string.IsNullOrWhiteSpace(accountId))
            throw new ArgumentException("Account id is required.");

        if (openingBalance < 0)
            throw new ArgumentOutOfRangeException(nameof(openingBalance));

        AccountId = accountId;
        Balance = openingBalance;
    }

    public void Deposit(decimal amount)
    {
        if (amount <= 0)
            throw new ArgumentOutOfRangeException(nameof(amount));

        Balance += amount;
    }

    public bool TryWithdraw(decimal amount)
    {
        if (amount <= 0)
            return false;

        if (amount > Balance)
            return false;

        Balance -= amount;
        return true;
    }
}

var account = new BankAccount("ACC-100", 100m);
account.Deposit(50m);
Console.WriteLine($"Balance after deposit: {account.Balance}");

var success = account.TryWithdraw(200m);
Console.WriteLine($"Withdraw success: {success}");
Console.WriteLine($"Final balance: {account.Balance}");
```

Inheritance Caution

True or false: In senior backend C# design, inheritance should usually be the first tool you reach for when two classes share some code.

Polymorphism Pays

Polymorphism removes decision clutter

Polymorphism lets the caller depend on a contract instead of concrete branching logic.

A common interview example is replacing repeated switch or if blocks with strategies.

This connects directly to the earlier Microservices Architecture lesson: service code often needs different behaviors for payment providers, notification channels, retry policies, or pricing rules. Polymorphism can keep those choices isolated.

The key phrase:

The caller should know what it needs, not every possible way to do it.

```csharp
using System;
using System.Collections.Generic;

public interface IDiscountPolicy
{
    decimal Apply(decimal orderTotal);
}

public sealed class NoDiscount : IDiscountPolicy
{
    public decimal Apply(decimal orderTotal) => orderTotal;
}

public sealed class PercentageDiscount : IDiscountPolicy
{
    private readonly decimal _percent;

    public PercentageDiscount(decimal percent)
    {
        _percent = percent;
    }

    public decimal Apply(decimal orderTotal) => orderTotal * (1 - _percent);
}

public sealed class CheckoutService
{
    private readonly IDiscountPolicy _discountPolicy;

    public CheckoutService(IDiscountPolicy discountPolicy)
    {
        _discountPolicy = discountPolicy;
    }

    public decimal CalculateTotal(decimal orderTotal)
    {
        return _discountPolicy.Apply(orderTotal);
    }
}

var services = new List<CheckoutService>
{
    new CheckoutService(new NoDiscount()),
    new CheckoutService(new PercentageDiscount(0.15m))
};

foreach (var service in services)
{
    Console.WriteLine($"Total: {service.CalculateTotal(100m):C}");
}
```

Substitution Principle

Fill in the blank: The SOLID principle stating that objects of a base type should be replaceable with objects of a subtype without breaking correctness is the ____.

SOLID Interview Map

SOLID without sounding robotic

You do not need to recite SOLID like a poem. You need to explain the design pressure each principle addresses.

Principle Interview meaning Single Responsibility One reason to change Open/Closed Add behavior without rewriting stable code Liskov Substitution Subtypes must preserve expected behavior Interface Segregation Do not force clients to depend on methods they do not use Dependency Inversion Depend on abstractions for policy boundaries

For a senior C# backend developer, the strongest examples usually involve:

  • Controllers depending on application services, not EF Core details
  • Domain objects protecting invariants
  • Message handlers delegating to focused services
  • Azure Functions or hosted workers depending on interfaces
  • Tests using abstractions to verify behavior in CI/CD

SOLID Interview Map

Abstraction Boundary

A service class directly creates SqlConnection, calls a third-party payment SDK, formats emails, and updates domain state. Which principle is most clearly being violated?

Composition Wins

Prefer composition for reusable capabilities

Inheritance says: this type is a specialized version of another type.

Composition says: this type uses another capability to do its job.

In backend systems, composition is often safer because you can replace collaborators independently. That fits well with unit tests, dependency injection, and deployment confidence from the CI/CD lesson.

A good senior answer:

I use inheritance for true subtype relationships. For shared behavior, policies, integrations, and infrastructure concerns, I usually prefer composition through interfaces.

```csharp
using System;

public interface IAuditSink
{
    void Record(string message);
}

public sealed class ConsoleAuditSink : IAuditSink
{
    public void Record(string message)
    {
        Console.WriteLine($"AUDIT: {message}");
    }
}

public sealed class OrderService
{
    private readonly IAuditSink _auditSink;

    public OrderService(IAuditSink auditSink)
    {
        _auditSink = auditSink;
    }

    public void PlaceOrder(string orderId)
    {
        Console.WriteLine($"Order {orderId} placed.");
        _auditSink.Record($"Order {orderId} was placed at {DateTime.UtcNow:O}");
    }
}

var service = new OrderService(new ConsoleAuditSink());
service.PlaceOrder("ORD-42");
```

Final Cheat Sheet

How to answer OOP questions well

Use this structure in interviews:

  1. Define the concept simply
  2. Explain the design problem it solves
  3. Give a C# backend example
  4. Mention the tradeoff
Fast answers
  • Encapsulation: protect state and business invariants.
  • Abstraction: expose a stable contract and hide unnecessary detail.
  • Inheritance: model true subtype relationships; use carefully.
  • Polymorphism: call behavior through a common contract.
  • Composition: build behavior by combining focused collaborators.
  • SOLID: guidelines for changeable, testable code — not laws.
Senior-level closing thought

Good OOP is not about making everything abstract. It is about putting responsibilities in the right place so the system can evolve safely.

That ties the course together: CI/CD gives you delivery safety, Azure gives you platform options, advanced C# gives you implementation power, microservices give you architecture boundaries, and OOP helps keep the code inside those boundaries maintainable.

Final Cheat Sheet

Open interactive view →