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

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.
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.
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.
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.
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.
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.
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.
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
Let's build something together.