Back to Blog
GuidesJuly 14, 20263 min read

SOLID Principles Explained with Real .NET Examples

This blog explains all 5 SOLID principles (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) with actual C# code examples showing both the violation and the fix — not just definitions. It highlights the Rectangle/Square Liskov trap and connects Dependency Inversion directly to why ASP.NET Core uses constructor injection, since that's a common follow-up question. It ends by framing this as a "rehearsal problem, not a knowledge problem" and positions Kiriti AI's adaptive mock interviews and cheatsheets as the way to practice spotting these violations live under pressure.

Share this article:

SOLID Principles Explained with Real .NET Examples

Ask any .NET developer if they know SOLID, and almost everyone says yes. Ask them to explain one principle with an actual code example under interview pressure, and the confidence usually drops fast.

That gap — between recognizing the acronym and actually applying it — is exactly what interviewers are probing for. Here's SOLID explained the way it should have been taught the first time: with real code, real trade-offs, and the mistakes that actually happen in production.

S — Single Responsibility Principle

A class should have one, and only one, reason to change.

The violation you'll actually see in real codebases:

public class OrderService
{
    public void CreateOrder(Order order) { /* business logic */ }
    public void SendConfirmationEmail(Order order) { /* email logic */ }
    public void SaveToDatabase(Order order) { /* data access logic */ }
}

This class changes if the business rules change, if the email provider changes, or if the database changes. Three reasons to change, three responsibilities crammed into one class.

The fix:

public class OrderService
{
    private readonly IEmailService _emailService;
    private readonly IOrderRepository _repository;

    public void CreateOrder(Order order)
    {
        _repository.Save(order);
        _emailService.SendConfirmation(order);
    }
}

Now OrderService orchestrates, while IEmailService and IOrderRepository each own a single concern. If email logic changes, OrderService never needs to be touched.

O — Open/Closed Principle

Classes should be open for extension, but closed for modification.

The violation:

public class DiscountCalculator
{
    public decimal Calculate(string customerType, decimal amount)
    {
        if (customerType == "Regular") return amount * 0.95m;
        if (customerType == "Premium") return amount * 0.90m;
        if (customerType == "VIP") return amount * 0.80m;
        return amount;
    }
}

Every new customer type means editing this method again — and every edit risks breaking existing logic that was already tested and working.

The fix:

public interface IDiscountStrategy
{
    decimal Apply(decimal amount);
}

public class VipDiscount : IDiscountStrategy
{
    public decimal Apply(decimal amount) => amount * 0.80m;
}

New customer types become new classes, not edits to existing, working code. This is the principle interviewers most often test with a "how would you add a new type without touching existing code" follow-up.

L — Liskov Substitution Principle

Subtypes must be substitutable for their base types without breaking the program.

The classic violation — the Rectangle/Square problem:

public class Rectangle
{
    public virtual int Width { get; set; }
    public virtual int Height { get; set; }
}

public class Square : Rectangle
{
    public override int Width { set { base.Width = base.Height = value; } }
    public override int Height { set { base.Width = base.Height = value; } }
}

A Square technically "is a" Rectangle, but if code assumes setting Width doesn't affect Height, substituting a Square silently breaks that assumption. This is a well-known interview trap precisely because it looks correct at first glance.

The practical lesson: inheritance should model genuine behavioral compatibility, not just a conceptual "is-a" relationship. If a subtype changes the expected behavior of the base type, favor composition over inheritance instead.

I — Interface Segregation Principle

Clients shouldn't be forced to depend on methods they don't use.

The violation:

public interface IWorker
{
    void Work();
    void Eat();
}

public class RobotWorker : IWorker
{
    public void Work() { /* fine */ }
    public void Eat() { throw new NotImplementedException(); }
}

RobotWorker is forced to implement Eat(), a method that makes no sense for it, purely because the interface bundled unrelated behaviors together.

The fix: split into smaller, focused interfaces — IWorkable and IFeedable — so classes only implement what's actually relevant to them.

D — Dependency Inversion Principle

High-level modules shouldn't depend on low-level modules. Both should depend on abstractions.

The violation:

public class OrderService
{
    private SqlOrderRepository _repository = new SqlOrderRepository();
}

OrderService is now hard-wired to SQL Server. Swapping databases, or writing a unit test with a mock repository, becomes painful or impossible.

The fix:

public class OrderService
{
    private readonly IOrderRepository _repository;

    public OrderService(IOrderRepository repository)
    {
        _repository = repository;
    }
}

This is also the foundation of why dependency injection exists in ASP.NET Core — the entire built-in DI container is essentially Dependency Inversion applied at the framework level. Interviewers often ask this exact question: "why does ASP.NET Core push you toward constructor injection?" The answer is this principle.

Why This Trips People Up in Interviews Specifically

Almost everyone can recite "Single Responsibility means one reason to change." Very few can, on the spot, look at an unfamiliar piece of code and immediately identify which principle it violates and how to fix it — because that requires pattern recognition built through repetition, not memorization.

This is a rehearsal problem, not a knowledge problem. You already know SOLID. The gap is being able to apply it fluently, out loud, while someone's evaluating you in real time.

Kiriti AI's domain-specific cheatsheets cover exactly this kind of applied, example-driven content instead of dry definitions, and the adaptive mock interview will push you with a real code snippet and ask you to spot the violation live — the way an actual interviewer would.

Try a free mock interview at kiritiai.com and see how quickly you can spot a SOLID violation under real interview pressure.

Subscribe to our newsletter

Get notified when we publish new engineering resources, guides, and platform updates.