Is SOLID Still Solid?

Somewhere early in your career (usually just after learning what a factory pattern is and just before realising that not every class needs an interface) you get handed a set of principles. Five of them. Bolded. Tidy. Helpful.

Is SOLID Still Solid?

S.O.L.I.D.

  • Single Responsibility Principle

  • Open/Closed Principle

  • Liskov Substitution Principle

  • Interface Segregation Principle

  • Dependency Inversion Principle

Together, they're meant to make code easier to reason about, extend and maintain. And often, they do.

But recently, some devs are asking: are these principles still enough? Do they still serve us, or are we serving them? And like all good questions in software, the answer is: it depends.

Principles, not prescriptions

SOLID wasn't handed down on stone tablets. It emerged from years of object-oriented design discussions, especially for languages like Java and C# where class-based hierarchies were dominant and dependency management was a full-time job.

In that context? Absolutely. These rules helped untangle god classes, reduce coupling and make testing not totally miserable.

But not every modern language faces those same pressures.

SOLID principles were conceived for object-oriented programming, and as one developer put it: "JavaScript doesn't really have either [classes or interfaces]. What we often think of as 'classes' in JS are merely class look-alikes simulated using its prototype system, and interfaces aren't part of the language at all."

Do you need an interface for everything in TypeScript? Probably not. Does your ten-line Python function need its own class to be "open for extension"? Also no. Should every React component follow a strict single responsibility? Depends if you enjoy abstraction for its own sake.

When we force SOLID on everything, we start optimising for patterns, not outcomes.

What still holds up?

Let's not throw the baby out with the SOLID bathwater.

The spirit of Single Responsibility- "this thing should do one job well" -is still sound.

Liskov? Vital when working in inheritance-heavy environments (Java says hello).

Dependency inversion? Key for testing and loose coupling in almost any stack.

But their expression should evolve with the language and the platform.

A well-composed function in Go might express these values without a single class. A set of closures in JavaScript might handle dependency injection without ever mentioning "interfaces".

Understanding the why behind each principle is more important than rote application.

When SOLID goes wrong

The criticism isn't new. Back in 2009, Joel Spolsky famously called SOLID "extremely bureaucratic programming that came from the mind of somebody that has not written a lot of code, frankly" on a Stack Overflow podcast. While the comment sparked a heated debate and apology, the underlying concern remains valid.

When SOLID principles are applied dogmatically, developers can end up with "over engineered pieces of crap": code that follows all the rules but solves simple problems with unnecessary complexity. As one critic noted:

"Do we need to inherit all the shapes from the Shape parent class? I mean, do we ever plan to iterate through a list of Shapes and calculate the area and volume for them? If not, the inheritance has absolutely no point."

The misapplication is so common that developers warn: "Applying them excessively or dogmatically can lead to over-engineering, unnecessary complexity, and reduced productivity."

Real-world examples include:

  • Creating interfaces for every single class, even when there's only one implementation

  • Breaking down simple functions into multiple classes to satisfy SRP

  • Building elaborate inheritance hierarchies that will never be extended

Layering modern practices

If SOLID is the foundation, what's the scaffolding?

Functional programming gives us immutability and pure functions: easier to test, reason about and compose. In functional contexts, SOLID principles need reinterpretation- dependency inversion becomes "functions depend on abstractions (interfaces) rather than concrete implementations."

Domain-driven design offers richer modelling, helping us name the real problems, not just their implementations.

Event-driven and reactive paradigms change how we handle dependencies and flow altogether.

Modern frameworks (NestJS, Spring Boot, React) offer structure out-of-the-box, but they also push developers toward pattern inflation if not checked.

Good architecture isn't about ticking boxes. It's about being deliberate.

You don't earn clarity by applying every rule… you earn it by knowing which rules to bend, and when.

Enter CUPID: Properties Over Principles

While we're questioning SOLID's modern relevance, it's worth mentioning that some developers aren't just adapting it... they're replacing it entirely.

Dan North, creator of Behaviour-Driven Development, has proposed CUPID as an alternative to SOLID. But instead of more rigid principles, CUPID offers "properties"- qualities that make code joyful to work with.

CUPID stands for:

  • Composable: Plays well with others. Small surface area, intention-revealing, minimal dependencies.

  • Unix philosophy: Does one thing well. Simple, consistent model focused on single-purpose rather than arbitrary "single responsibility."

  • Predictable: Does what you expect. Behaves consistently, deterministically and observably.

  • Idiomatic: Follows the natural patterns and conventions of your language or framework.

  • Domain-based: The code reflects the problem domain, not just technical abstractions.

The key insight? Properties are goals to move towards, not binary rules to follow. Your code can be "more composable" or "less predictable": there's a clear direction of improvement without the all-or-nothing compliance mentality that SOLID can encourage.

North argues that SOLID creates "bureaucratic programming" while CUPID focuses on code that humans actually enjoy working with. Instead of asking "does this follow the Single Responsibility Principle?", you ask, "does this do one thing well in a way that's natural for this language?"

The practical difference? A React component that handles both data fetching and rendering might violate SRP, but if it's predictable, composable and idiomatic for React patterns, CUPID would consider it fine. Context trumps compliance.

Whether CUPID replaces SOLID or just provides a gentler lens for thinking about code quality, it represents a shift toward more human-centred, context-aware development practices.

Context matters more than compliance

Here's the thing: SOLID makes perfect sense for certain contexts. If you're building reusable libraries, working on large teams or maintaining long-lived enterprise systems, these principles shine.

But as one experienced developer noted:

"If you do not follow these principles there is no guarantee that your software will automatically exhibit problems which make it unmaintainable and unextensible. If you do follow these principles there is no guarantee that your software will automatically become more maintainable and more extensible."

The key insight? Context should drive your approach. A quick prototype, a simple script or a focused microservice might benefit from simpler approaches. Complex enterprise systems with multiple teams probably need more structure.

So, is SOLID still useful?

Absolutely. But only if you treat it as a tool, not a contract.

The best engineers don't just "follow principles". They ask:

  • What's the trade-off of applying this pattern here?

  • Will this abstraction help a future developer… or hide complexity?

  • Does this language or framework give me these guarantees already?

  • Am I solving a real problem or just following a rule?

If you're working in a statically typed, interface-heavy language with layered architecture and a big codebase, SOLID is still your friend.

But if you're knee-deep in functional TypeScript or writing microservices that pass JSON over HTTP… you might want to keep the spirit, not the ceremony.

For the coffee queue

If you're looking for discussion fuel, ask your team:

  • Should new developers still be taught SOLID first?

  • Which principle do you find easiest to misuse?

  • When did you last see a codebase suffer because someone followed a pattern too strictly?

  • How do you decide when a principle helps vs hurts?

And if you don't think those questions matter, just ask yourself: is your current architecture serving your team or just living rent-free in your dependency injection container?

Until next time, code with context, not commandments.

blog author

Andrew Paul

Software Engineering Trainer

Related Articles