SOLID Principles

Five object-oriented design principles that together guide maintainable, extensible, and testable code.

The Five Principles

AcronymPrincipleShort RuleWhat It Prevents
SSingle Responsibility PrincipleOne reason to changeGod classes; tangled concerns; untestable modules
OOpen-Closed PrincipleOpen for extension, closed for modificationRegression-prone change cascades; fragile conditionals
LLiskov Substitution PrincipleSubtypes must substitute safelyBroken polymorphism; defensive isinstance checks
IInterface Segregation PrincipleNo forced dependency on unused methodsFat interfaces; stub-filled implementations
DDependency Inversion PrincipleDepend on abstractions, not concretionsUntestable business logic; tightly coupled layers

Origins

The SOLID acronym was coined by Michael Feathers around 2000 and popularized by Robert C. Martin (“Uncle Bob”), who assembled and refined the five principles from earlier work in the object-oriented design community.

The individual principles predate the acronym:

  • SRP was influenced by David Parnas’s 1972 paper on information hiding and modular decomposition.
  • OCP was first stated by Bertrand Meyer in Object-Oriented Software Construction (1988).
  • LSP was formalized by Barbara Liskov and Jeannette Wing in their 1994 paper “A Behavioral Notion of Subtyping.”
  • ISP emerged from Martin’s consulting work at Xerox in the early 1990s, where a large printer job scheduler interface was causing widespread build failures.
  • DIP was articulated by Martin in his 1996 article in C++ Report.

Martin’s 2000 paper “Design Principles and Design Patterns” (often called the “Uncle Bob paper”) presented them as a unified framework for maintainable OO design. They were later elaborated in Agile Software Development, Principles, Patterns, and Practices (2002).

How They Interact

SOLID principles are not independent — they reinforce each other as a system:

SRP enables the others. When each class has a single, clear responsibility, it becomes easier to apply OCP (there is only one axis of extension to manage), LSP (the behavioral contract is smaller and more precise), ISP (interfaces align with single responsibilities), and DIP (dependencies on single-purpose abstractions are well-defined).

OCP depends on DIP. The mechanism for “open for extension” is typically an abstraction (interface). DIP provides the design rule for how to structure those abstractions — high-level code depends on them, low-level code implements them.

LSP validates OCP. If new implementations (extensions) violate the behavioral contract of the base type, then the system is not truly extended safely — it is broken. LSP is the correctness criterion that OCP-based extension must satisfy.

ISP refines DIP. If high-level modules depend on large, fat interfaces (DIP satisfied technically), but they only use 20% of the interface, ISP is violated. Combining DIP and ISP means: depend on the smallest abstraction that captures exactly what you need.

A practical chain:

SRP → classes have clear responsibilities
  → ISP → interfaces model those responsibilities narrowly
    → DIP → high-level code depends on those narrow interfaces
      → OCP → new behavior = new class implementing the interface
        → LSP → the new class must honor the interface's behavioral contract

Criticisms and Nuances

Over-application creates complexity. Applying SOLID mechanically everywhere produces an explosion of small classes, interfaces, and indirection that can make a simple codebase harder to navigate than the violation it was meant to fix. SOLID principles are heuristics, not laws.

SOLID works best at scale. For a script, a simple CRUD endpoint, or an internal utility, SOLID’s benefits (testability, extensibility) often do not justify its costs (abstractions, boilerplate). The principles are most valuable in large, long-lived codebases maintained by teams.

SRP’s “reason to change” is subjective. What counts as a single responsibility depends on the level of granularity. Martin acknowledges this: SRP is about actors (stakeholders who can demand a change), not purely technical concerns. Two developers may legitimately disagree about whether a class has one responsibility or two.

OCP as originally stated is impractical. Meyer’s original formulation (truly never modify) is unrealistic. Martin’s restatement (don’t modify in response to new extensions) is more practical but still requires judgment about which extensions are “expected.”

LSP violations are often fixable only by redesign. By the time an LSP violation is discovered in a production system, it is often deeply embedded. The principle is most valuable as a design-time constraint, not a diagnostic.

DIP ≠ always use a DI framework. Many developers interpret DIP as “use Spring” or “use a container.” DIP is a structural design principle; Dependency Injection is the technique, and a framework is just tooling.

Unverified

Some sources attribute the original SRP formulation to Tom DeMarco’s concept of cohesion from structured design (1979). The exact lineage from DeMarco to Martin is not fully settled in the literature.

When to Apply SOLID

ContextGuidance
Long-lived production codebaseApply consistently
Team-maintained service or libraryApply selectively — especially SRP, DIP
Internal script or utilityApply KISS/YAGNI first; SOLID if it grows
Prototype / spikeDefer — don’t over-engineer throwaway code
Framework/library with public APIApply rigorously — especially ISP, OCP, LSP