SOLID Principles

Concept

Five design guidelines for dividing object-oriented code so that what you build stays cheap to change later.

Why it exists

How you carve code into pieces is not a problem at the start. With twenty files you remember where everything lives, and when you change one place you can follow the consequences by eye.

Six months later it is different. You open the file that handles payments to add one discount rule, and it runs 800 lines, holding amount calculation, receipt emails, audit logging, and currency conversion together. Adding the discount condition means reading all 800 lines, and once you have changed it the receipt email stops going out.

The worse part is how gradually this arrives. Each time a feature was added it went into the nearest file, and fifty of those decisions later the file is one nobody wants to open. The signals people call code smells come from here.

In the early 2000s that experience got written up as five guidelines. Each has a name, and the first letters spell SOLID. It is less a technology than a set of rules somebody wrote down after living through the consequences.

How it works

Each of the five is one question you put to the code. When the answer is bad, you move where the cut is.

  1. The single responsibility principle asks how many reasons this piece has to change. Amount calculation changes when policy changes; receipt emails change when wording changes. Two reasons means it should be two pieces, and once they are split, a wording change to the receipt can no longer make an amount wrong. The measure for how well that question was answered is cohesion.
  2. The open-closed principle asks whether adding a new case means editing existing code. If every new discount type adds another branch inside the calculation function, the answer is yes. Restructure so that a new discount is a new file and nothing else, and adding one never means opening the old one. The test is mechanical: count how many existing files a new case forces you to touch.
  3. The Liskov substitution principle asks whether the program still behaves when a child is dropped into the parent's place. Make a square that inherits from a rectangle, and code that sets only the width to 300 will find the height changed as well, producing a wrong result. An inheritance relationship that reads sensibly in a diagram can still fail this question in code.
  4. The interface segregation principle asks whether callers are forced to carry capabilities they never use. Expose one interface with twenty methods and a caller using two of them is still affected every time any of the twenty changes.
  5. The dependency inversion principle asks whether the upper-level rules know about lower-level implementations directly. If discount calculation calls a specific database directly, swapping the store means editing the calculation rules too. Put an interface between them and have both sides know only that, and either side can be replaced without touching the other, which is what turns a storage swap into a local change rather than a project. The measure for that distance is coupling.

One thing runs through all five: drawing a line in advance between what will change and what will not. The five questions are different angles on where that line currently sits.

Where it shows up

The most frequent setting is code review. "I don't like this class" and "this class looks like it has two reasons to change" land completely differently. The second is checkable rather than a matter of taste, which moves the conversation to a next step instead of a standoff. Naming the question also lets the author argue with the question rather than with the reviewer.

It also serves as the criterion during refactoring. That work changes structure while leaving behavior alone, and with nothing to say which direction to move things, people move them by preference, and a cleanup week ends with a different structure per person who worked on it. Put the five questions to a smell in order and usually one of them catches, and the one that catches tells you which way to go.

In design discussions it pairs with design patterns. Patterns are a catalog of solutions to recurring problems; these principles are the basis for judging whether a solution is warranted at all. When the boundaries being drawn are larger than classes, an approach like domain-driven design, which takes the business vocabulary as the dividing line, takes over this role.

Where it does not apply is equally clear. A script you will run once and delete, or early code whose requirements are not settled, is not worth the treatment. Honoring these principles costs file count and layers of indirection, and in code that will never change, the cost is all that remains.

How it sounds at work

"This class looks like it has two reasons to change. Let's pull the email sending out."

The single responsibility question, translated into a review comment.

"This branch grows every time a discount is added. Let's restructure so discounts live separately."

Pointing out that a new case is being handled by editing existing code.

"This is a script, so it doesn't need the full treatment. We run it once and delete it."

A judgment that this is not the place to pay the cost.

At Pagedeck, Theo worked through these questions in order while cleaning up the payment code. He pulled receipt emails and audit logging out of the 800-line file first, then split discount rules into one file per type, so adding a discount no longer means opening anything that already works.

He left the document storage code alone. Its structure has not changed in three years and nothing on the roadmap changes it, so the value of splitting did not exceed the value of leaving it whole.

What people get wrong

The most common error is believing all five must be honored at all times. They are called principles and they arrive as a set of five, which invites that reading. But each carries a price. Introducing an interface means readers skip a level while following the code; splitting one file into three means opening three. Pay that price in code that will never change and complexity is the only thing you get. A 100-line feature wrapped in five interfaces and twelve files has not honored the principles — it has created technical debt in a different shape. The cost is invisible in the diff and obvious six months later to whoever has to read it.

The second error is treating this as an object-oriented-only conversation. The explanations run on classes and inheritance, so the assumption is natural. But the five questions are about boundaries rather than classes. How many reasons this piece has to change, and whether a new case means editing an old one, are the same questions when splitting functions and when splitting services. Working in a language with no classes at all, you can still put all five to your code.

Where to go next

  • Software architecture

    see where design knowledge sits among the four groupings and what it supplements

  • Design pattern

    the catalog these principles judge, so reading both sets the order to use them in

Last updated 2026-09-16