SOLID principles

Also known as: SOLID

On this page 7

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.

PrincipleCore ruleProblem it prevents
Single responsibility (SRP)A module should have one reason to changeUnrelated changes colliding in one class
Open-closed (OCP)Extend behavior without modifying existing codeEdits rippling through tested code
Liskov substitution (LSP)Subtypes must honor the contract of their base typeSurprising behavior from subclasses
Interface segregation (ISP)Clients should not depend on methods they do not useBloated interfaces and needless recompilation
Dependency inversion (DIP)High-level policy depends on abstractions, not detailsBusiness logic tied to databases or frameworks
Summary of the five principles

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 smellViolated principleTypical refactoring
Class with many unrelated methodsSingle responsibilitySplit by actor or reason to change
Long switch on a type fieldOpen-closedReplace with polymorphic implementations
Subclass throwing NotSupportedLiskov substitutionRework the hierarchy or use composition
Interface with dozens of methodsInterface segregationSplit into role-specific interfaces
Domain class creating a database clientDependency inversionInject an abstraction through the constructor
Typical code smells and the principle that addresses them

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.

ConceptScaleRelation to SOLID
Separation of concernsAny scaleGeneral idea that SRP makes concrete for classes
Design patternsSmall groups of classesReusable solutions that often apply SOLID
Inversion of controlFramework and applicationBroader idea behind dependency inversion
Clean architectureWhole applicationApplies dependency inversion to layers
Hexagonal architectureWhole applicationIsolates the core behind ports and adapters
Where SOLID fits among design concepts

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.

SituationRecommended approachReason
Stable utility codeKeep it simple and directAbstractions would never be used
Second variant appearsIntroduce an interface nowReal variation justifies the abstraction
External system dependencyWrap behind an interfaceEnables testing and replacement
Large shared interfaceSplit by client roleReduces coupling between teams
Prototype or throwaway codeDefer structureSpeed matters more than longevity
When to invest in SOLID structure

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.