DEV Community

Abhinaw
Abhinaw

Posted on • Originally published at bytecrafted.dev on

SOLID Principles Simplified: Cheatsheet + Interview Guide

Every developer can recite what SOLID stands for, but in interviews and real-world reviews, it's the practical application that matters. Here’s my no-fluff cheatsheet with code smells, fixes, and analogies I use in reviews.

Want the PDF version? Click here to download

S: Single Responsibility Principle (SRP)

  • Real Meaning: One reason to change, not one "thing" it does.
  • Why It Matters: Avoids "God classes" that block clean PRs & slow refactoring.
  • Personal Analogy: "If you can't give a clean commit message for the change, it's violating SRP."
  • Code Smell: Method/class summary has multiple and/or.
  • Actionable: Before adding a method, ask: "Is this a different concern?"
  • Read more on SRP
  • Short link: bytecrafted.dev/solid-srp
// Bad: Class doing too much (data + printing)
public class Report {
    public string Content { get; set; }
    public void Print() => Console.WriteLine(Content);
}

// Good: Split responsibilities
public class Report { public string Content { get; set; } }
public class ReportPrinter 
{ 
    public void Print(Report r) => Console.WriteLine(r.Content); 
}

Enter fullscreen mode Exit fullscreen mode

O: Open/Closed Principle (OCP)

  • Real Meaning: Add features by extension, not by editing old code.
  • Why It Matters: Keeps legacy code stable; new business rules plug in cleanly.
  • Personal Analogy: "If a new requirement means touching brittle switch statements, you're not OCP."
  • Code Smell: Growing switch/if chains for types or behaviors.
  • Actionable: When adding a rule, prefer new handler/class over changing the old one.
  • Read more on OCP
  • Short link: bytecrafted.dev/solid-ocp
// Bad: Must edit method every time a new shape is added
public double Area(object shape) {
    if (shape is Circle c) return Math.PI * c.Radius * c.Radius;
    if (shape is Square s) return s.Side * s.Side;
    return 0;
}

// Good: Add new shapes by extension
public interface IShape { double Area(); }
public class Circle : IShape 
{
  public double Radius { get; set; }
  public double Area() => Math.PI * Radius * Radius; 
}
public class Square : IShape 
{
  public double Side { get; set; } 
  public double Area() => Side * Side; 
}
Enter fullscreen mode Exit fullscreen mode

L: Liskov Substitution Principle (LSP)

  • Real Meaning: Subtypes must behave as expected, no surprises for callers.
  • Why It Matters: Swapping implementations shouldn't break existing tests or runtime logic.
  • Personal Analogy: "If a subclass throws where the base returns null, that's an LSP landmine."
  • Code Smell: Derived classes override with different exceptions, parameters, or semantics.
  • Actionable: Run parent class tests on every subclass; look for broken guarantees.
  • Read more on LSP
  • Short link: bytecrafted.dev/solid-lsp
// Bad: Subclass breaks expectations
public class Bird { public virtual void Fly() { } }
public class Penguin : Bird 
{
   public override void Fly() => throw new NotSupportedException(); 
}

// Good: Refactor hierarchy
public abstract class Bird { }
public class FlyingBird : Bird { public void Fly() { /* ... */ } }
public class Penguin : Bird { public void Swim() { /* ... */ } }

Enter fullscreen mode Exit fullscreen mode

I: Interface Segregation Principle (ISP)

  • Real Meaning: Small, client-focused interfaces, never force unused methods.
  • Why It Matters: Reduces coupling, makes mocks/tests trivial, avoids NotSupportedException landmines.
  • Personal Analogy: "If your interface summary needs bullet points, it's already too fat."
  • Code Smell: Implementations with empty or throw NotSupportedException methods.
  • Actionable: Extract groups of related methods into separate interfaces as soon as a client skips one.
  • Read more on ISP
  • Short link: bytecrafted.dev/solid-isp
// Bad: Fat interface forces unused methods
public interface IMachine {
    void Print();
    void Scan();
    void Fax();
}

public class BasicPrinter : IMachine {
    public void Print() { }
    public void Scan() => throw new NotSupportedException();
    public void Fax() => throw new NotSupportedException();
}

// Good: Split into smaller interfaces
public interface IPrinter { void Print(); }
public interface IScanner { void Scan(); }

public class BasicPrinter : IPrinter {
    public void Print() { }
}

Enter fullscreen mode Exit fullscreen mode

D: Dependency Inversion Principle (DIP)

  • Real Meaning: Depend on abstractions, not concrete implementations, flip the usual control.
  • Why It Matters: Makes business logic testable, swappable, and free of infrastructure glue.
  • Personal Analogy: "If you see new SqlRepo() in a service, that's DIP going up in flames."
  • Code Smell: Direct instantiation of dependencies inside business logic.
  • Actionable: Use constructor injection for every external dependency; mock in tests, swap in production.
  • Read more on DIP
  • Short link: bytecrafted.dev/solid-dip
// Bad: High-level depends on low-level directly
public class ReportService {
    private readonly SqlReportRepo _repo = new SqlReportRepo();
}

// Good: Depend on abstraction, inject implementation
public interface IReportRepo { /* ... */ }
public class SqlReportRepo : IReportRepo { /* ... */ }

public class ReportService {
    private readonly IReportRepo _repo;
    public ReportService(IReportRepo repo) { _repo = repo; }
}
Enter fullscreen mode Exit fullscreen mode

Read full series: bytecrafted.dev/series/solid.

Further Reading

Top comments (0)