SOLID Principles
What It Does
SOLID is an acronym for five object-oriented design principles assembled by Robert C. Martin (“Uncle Bob”) in the early 2000s, drawing on earlier work by others:
- S — Single Responsibility Principle (SRP): A class should have only one reason to change. (Tom DeMarco / Meilir Page-Jones, 1970s cohesion concept)
- O — Open/Closed Principle (OCP): Software entities should be open for extension but closed for modification. (Bertrand Meyer, 1988)
- L — Liskov Substitution Principle (LSP): Objects of a supertype should be replaceable with objects of a subtype without altering program correctness. (Barbara Liskov, 1987)
- I — Interface Segregation Principle (ISP): Clients should not be forced to depend on interfaces they do not use.
- D — Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules; both should depend on abstractions.
Together they aim to produce class structures that are loosely coupled, testable, and amendable to change without cascading modifications. They are most naturally expressed in statically-typed OO languages (Java, C#, Kotlin) and have been influential in enterprise software design since the mid-2000s.
Key Features
- DIP is the structural anchor: Dependency inversion (depend on abstractions, inject concrete implementations) is the mechanism most directly enabling testability and modularity; SRP, OCP, and ISP are often achieved as downstream consequences of good DI.
- LSP enforces semantic contracts: Liskov’s principle goes beyond syntactic type compatibility to require that a subtype does not violate the behavioral expectations of the supertype (e.g., a Square subclass of Rectangle is syntactically valid but violates LSP by breaking width/height independence).
- SRP is the most contested: Dan North and others have called SRP “pointlessly vague” because “one reason to change” is not operationally defined. Different developers draw class boundaries differently under the same principle.
- Language-dependent applicability: SOLID is most applicable in class-based OO languages. Functional languages (Haskell, Clojure, Elm) achieve similar goals through different mechanisms (pure functions, immutable data, type classes). Python duck typing weakens the leverage of ISP and LSP.
- SOLID vs. YAGNI tension: Strict SRP and OCP compliance often requires creating abstractions for future extension points that never materialize, violating YAGNI. This tension is a common source of over-engineered enterprise Java.
Use Cases
- Large Java/C# enterprise codebases: SOLID shines in N-tier enterprise applications with hundreds of classes, multiple teams, and multi-year maintenance horizons where coupling rot is the primary quality risk.
- Testing strategy: DIP (inject dependencies) is a prerequisite for unit testing without mocking frameworks that patch real implementations. Well-applied DIP enables test isolation without test-specific production hacks.
- Plugin/extension architectures: OCP applies naturally when building plugin systems (VS Code extensions, IntelliJ plugins) where core behavior must remain stable while functionality is extended by third parties.
- Onboarding material: SOLID is widely taught in computer science education and bootcamps; using it as shared vocabulary reduces friction when discussing class design in code review.
Adoption Level Analysis
Small teams (<20 engineers): Partial application is appropriate. DIP for testability and a reasonable SRP interpretation are valuable. Strict OCP compliance with abstract factories and strategy patterns adds ceremony that slows small teams without proportional benefit. YAGNI should dominate over strict SOLID in early-stage products.
Medium orgs (20–200 engineers): Most applicable here. Shared codebases with multiple teams benefit from clear class responsibilities (SRP) and dependency injection (DIP) to reduce coupling across module boundaries. ISP prevents “fat interfaces” from becoming cross-team contracts.
Enterprise (200+ engineers): SOLID is often institutionalized in enterprise architecture guidelines, sometimes dogmatically. The risk at this scale is ossification — abstract factories and service locators proliferating without genuine extension points, creating navigation overhead. Domain-Driven Design bounded contexts often provide more structural guidance than SOLID alone.
Alternatives
| Alternative | Key Difference | Prefer when… |
|---|---|---|
| Functional programming principles (pure functions, immutability) | Achieves loose coupling and testability without class hierarchies | Using functional languages or functional-style Python/JS; avoids OOP inheritance altogether |
| Domain-Driven Design (DDD) | Focuses on domain model boundaries and aggregate design rather than class-level responsibility | Complex business domains where class granularity matters less than bounded context alignment |
| GRASP Patterns | Assigns responsibilities based on information expert, creator, controller, low coupling, high cohesion | Teams wanting a more prescriptive, information-flow-based design guide than SOLID |
| Simple Design (Kent Beck) | Four rules: passes tests, reveals intention, no duplication, fewest elements | Pragmatic teams preferring emergent design over upfront abstraction |
Evidence & Sources
- Evaluating the Application of SOLID Principles in Modern AI Frameworks (arXiv 2025)
- SOLID — Wikipedia
- In Defense of SOLID Principles — NDepend
- SOLID Principles: Don’t Follow Them Blindly — various practitioner critiques aggregated at Quora
Notes & Caveats
- Empirical evidence that SOLID-adherent codebases have measurably lower defect rates or maintenance costs is absent. SOLID is a design heuristic backed by decades of practitioner experience, not a controlled study. Do not cite it as proven fact in architectural justifications.
- A 2025 arXiv study found modern AI frameworks (LangChain, LlamaIndex, AutoGen) explicitly trade off SOLID compliance for performance and flexibility — suggesting that SOLID’s value depends on the stability-vs.-exploration phase of the system.
- Over-applying SOLID in microservices contexts can produce “nano-services” (one class, one service) that distribute complexity to the network layer while reducing it in code. The right granularity for SOLID is the module/class, not the service boundary.
- Robert C. Martin’s later work and public statements have become controversial in the software community for reasons unrelated to SOLID. Evaluate the principles on their own merits, independent of the author’s current standing.