Software Engineering

SOLID Principles Explained with TypeScript (No Jargon)

The 5 SOLID principles explained simply with concrete TypeScript examples — and when you should NOT apply them.

Aymane Atigui
Aymane Atigui
·July 24, 2026·7 min read

Every developer has nodded through a SOLID explanation full of AbstractShapeFactory examples and retained nothing. I use these principles daily in production TypeScript — Next.js apps, Node APIs — so here they are with examples from code you actually write, plus the part most articles skip: when applying them is a mistake.

SOLID is five heuristics for one goal: code you can change without fear. Apply them where change is likely; ignore them where it isn't.

S — Single Responsibility: one reason to change

The classic violation isn't a giant class — in modern TypeScript it's the 400-line React component that fetches data, transforms it, handles the form AND renders. When the API changes, you edit the same file as when the design changes. Split by reason to change: a hook for data (useOrders), a pure function for transforms, a component for rendering.

O — Open/Closed: extend without editing

Every time you add an else if (type === "newThing") to a growing chain, you're editing tested code to add behavior. The TypeScript-native fix is a record of handlers: const handlers: Record<PaymentType, Handler> — adding a payment method becomes adding an entry, not editing a function. Discriminated unions + exhaustive switch get you the same safety with compiler help.

L — Liskov Substitution: subtypes must not surprise

If your CachedUserRepo throws where UserRepo returned null, every caller now needs to know which one it got — the abstraction is broken. In TypeScript this is less about classes and more about honoring the interface contract: same inputs, same failure modes, no surprise side effects.

I — Interface Segregation: small contracts

A function that takes user: User but reads only user.email is lying about its needs. Take { email }: Pick<User, "email"> instead — callers stop building fake giant objects in tests, and refactors stop rippling. TypeScript's structural typing makes this almost free.

D — Dependency Inversion: depend on interfaces, not modules

The practical version: your business logic shouldn't import prisma directly everywhere. Pass a repository interface instead, and your logic becomes testable without a database. This is the one SOLID principle that pays for itself the first time you write a test.

When NOT to apply SOLID

An MVP with three screens doesn't need dependency inversion — it needs to ship. Abstractions cost indirection, and indirection costs onboarding time. My rule from years of production code: apply SOLID at the point of the second change, not the first write. The first time code changes for a new reason, that's your signal — refactor toward the principle then, with a real example in hand instead of a guess.

The honest summary

SOLID won't make you a senior. Knowing when the cure is worse than the disease will. If you want the same pragmatism applied to your codebase — TypeScript, React, Node — that judgment is exactly what I sell: my work · contact.

Topics

typescriptSOLIDArchitectureClean Code
Aymane Atigui

Aymane Atigui

Software Engineer, Technical Consultant & Product Designer based in Casablanca, Morocco.

Enjoyed this article?

Let's build something together.

Get in Touch