The SOLID principles are five object-oriented design guidelines that help developers structure classes and modules so that code stays easy to change, test and extend. The acronym stands for the Single responsibility, Open-closed, Liskov substitution, Interface segregation and Dependency inversion principles, and in software architecture they act as a shared vocabulary for judging whether a design will age well.
The principles were collected and popularized by Robert C. Martin in the early 2000s, building on earlier work such as Barbara Liskov’s substitution rule and Bertrand Meyer’s open-closed principle. They are heuristics, not laws: they describe forces that make code rigid or fragile and suggest ways to reduce them.
The five SOLID principles explained
Each principle addresses a different cause of costly change in programming. Together they aim at modules with high cohesion inside and low coupling between them.
| Principle | Core rule | Problem it prevents |
|---|---|---|
| Single responsibility (SRP) | A module should have one reason to change | Unrelated changes colliding in one class |
| Open-closed (OCP) | Extend behavior without modifying existing code | Edits rippling through tested code |
| Liskov substitution (LSP) | Subtypes must honor the contract of their base type | Surprising behavior from subclasses |
| Interface segregation (ISP) | Clients should not depend on methods they do not use | Bloated interfaces and needless recompilation |
| Dependency inversion (DIP) | High-level policy depends on abstractions, not details | Business logic tied to databases or frameworks |
The single responsibility principle is often misread as “a class should do one thing”. Its precise meaning is that a module should answer to one actor or source of change: a report class that both calculates figures for finance and formats pages for the design team has two reasons to change and should be split.
The Liskov substitution principle is the most formal. Code that works with a base type must keep working when given any subtype, which means a subtype may not strengthen preconditions, weaken postconditions or throw unexpected exceptions. The classic violation is a Square class inheriting from Rectangle, where setting the width silently changes the height.
How the principles work in code
In practice, the principles converge on programming against abstractions. A payment service, for example, depends on a PaymentGateway interface rather than on a concrete card processor class. New providers are added as new implementations, which satisfies the open-closed principle, and the service never imports the provider’s code, which satisfies dependency inversion.
Dependency injection is the usual mechanism that makes this practical: concrete implementations are created elsewhere and passed in through constructors. The result is that business logic can be unit tested with simple fakes instead of real databases, networks or payment systems.
| Code smell | Violated principle | Typical refactoring |
|---|---|---|
| Class with many unrelated methods | Single responsibility | Split by actor or reason to change |
| Long switch on a type field | Open-closed | Replace with polymorphic implementations |
| Subclass throwing NotSupported | Liskov substitution | Rework the hierarchy or use composition |
| Interface with dozens of methods | Interface segregation | Split into role-specific interfaces |
| Domain class creating a database client | Dependency inversion | Inject an abstraction through the constructor |
SOLID compared with related design concepts
SOLID operates at the level of classes and modules, while neighboring concepts in architecture work at different scales. Understanding the scale prevents applying the wrong tool.
| Concept | Scale | Relation to SOLID |
|---|---|---|
| Separation of concerns | Any scale | General idea that SRP makes concrete for classes |
| Design patterns | Small groups of classes | Reusable solutions that often apply SOLID |
| Inversion of control | Framework and application | Broader idea behind dependency inversion |
| Clean architecture | Whole application | Applies dependency inversion to layers |
| Hexagonal architecture | Whole application | Isolates the core behind ports and adapters |
Patterns such as Strategy, Adapter and Factory are concrete ways to satisfy the principles. Clean and hexagonal architecture take dependency inversion to the system level, so that the domain core knows nothing about user interfaces, databases or external services.
Applying SOLID with judgment
The principles bring the most value where change is frequent and expected, such as business rules, integrations and plugins. Applying them everywhere from the first line of code adds indirection without benefit.
| Situation | Recommended approach | Reason |
|---|---|---|
| Stable utility code | Keep it simple and direct | Abstractions would never be used |
| Second variant appears | Introduce an interface now | Real variation justifies the abstraction |
| External system dependency | Wrap behind an interface | Enables testing and replacement |
| Large shared interface | Split by client role | Reduces coupling between teams |
| Prototype or throwaway code | Defer structure | Speed matters more than longevity |
A useful rule among experienced developers is to wait for the second or third variation before abstracting. An interface created for a single implementation often guesses the wrong extension points and must be rewritten anyway.
Criticisms and common mistakes
SOLID attracts criticism mainly when it is applied mechanically. The most common problems are the following.
- Over-engineering: dozens of single-method interfaces and factories that make simple logic hard to follow.
- Misreading SRP as one method per class, which scatters cohesive logic across many files.
- Inheritance-first thinking, while most SOLID goals are easier to meet with composition.
- Ignoring the paradigm: in functional programming, many goals are achieved with functions and modules rather than class hierarchies.
- Treating the principles as metrics instead of as prompts for design discussion.
The principles remain valuable because they name recurring design forces. A team that can say “this breaks Liskov substitution” resolves design debates faster than one that only senses that something feels wrong.
The short version
The SOLID principles are five rules of thumb for organizing code so that it does not turn into a tangle. Think of a well-organized workshop: each tool has one job, new tools can be added without rebuilding the shelves, and any screwdriver of the right size fits the same screws.
Code built this way is easier to change without breaking something else. The rules are guidance, not commandments, and experienced developers apply them where change is likely, not everywhere.
Where it makes a difference
An online shop has an order class of about 2,000 lines that calculates prices, saves orders to the database, sends confirmation emails and calls one specific shipping carrier. Every change, even a new email text, requires retesting the entire checkout, and adding a second carrier would mean editing the most critical class in the system.
The team refactors over several sprints. Pricing, persistence and notifications become separate classes, shipping is accessed through a ShippingProvider interface, and implementations are injected at startup. When the business adds a second carrier, developers write one new class without touching the checkout code. Unit tests for pricing now run in seconds with fake providers, and the regression risk for routine changes drops noticeably because each change touches a single, focused module.