Skip to content

Design Principles

Good software is not just software that works - it is software that is easy to understand, change, and extend over time. Design principles are guidelines that help developers write code that is maintainable, flexible, and robust.

These principles are language-agnostic. Whether you write Python, Java, Go, or TypeScript, the underlying concepts apply equally.


SOLID is an acronym for five foundational object-oriented design principles, popularized by Robert C. Martin (“Uncle Bob”). Together, they guide developers toward writing code that is easy to reason about and change.

“A class should have one, and only one, reason to change.”

Every module, class, or function should be responsible for exactly one thing. If a class handles both business logic and database persistence, it has two reasons to change - violating SRP.

Bad: A UserService class that validates a user form, saves the user to a database, and sends a welcome email.
Good: Separate UserValidator, UserRepository, and EmailService classes, each with a single job.

“Software entities should be open for extension, but closed for modification.”

You should be able to add new behavior to a system without changing its existing, tested code. This is typically achieved through abstractions, interfaces, and polymorphism. Adding a new feature means writing new code, not editing old code.

“Objects of a subclass should be substitutable for objects of the parent class without breaking the application.”

If Bird has a fly() method and Penguin extends Bird, substituting a Penguin where a Bird is expected should not break anything - but it will, since penguins can’t fly. This signals a flawed inheritance hierarchy. LSP forces you to design inheritance trees that are semantically correct.

“A class should not be forced to implement interfaces it does not use.”

Prefer many small, specific interfaces over one large, monolithic “catch-all” interface. A Printer class shouldn’t be forced to implement a fax() method just because it implements a broad MachineInterface.

“Depend on abstractions, not concretions.”

High-level modules (business logic) should not depend on low-level modules (database drivers, HTTP clients). Both should depend on abstractions (interfaces). This makes it easy to swap implementations - for example, swapping a MySQL database for PostgreSQL - without changing business logic.

PrincipleCore Idea
Single ResponsibilityOne class, one job
Open/ClosedExtend behavior; don’t modify existing code
Liskov SubstitutionSubtypes must be safely substitutable
Interface SegregationSmall, focused interfaces
Dependency InversionDepend on abstractions, not implementations

Beyond SOLID, three additional principles are universally valued across all programming paradigms and languages.

“Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”

If the same logic appears in two places, it will inevitably get updated in one place and forgotten in another, creating inconsistency and bugs. DRY is not just about avoiding copy-pasted code - it’s about having a single source of truth for every business rule and piece of knowledge.

Violated by: Copy-pasting validation logic into three different controllers instead of creating a shared validate() function.

“Most systems work best if they are kept simple rather than made complex.”

Complexity is the enemy of reliability. Always prefer the simplest solution that solves the problem. Clever, overly-engineered code is harder to debug, harder to test, and harder for the next developer to understand.

Violated by: Using a complex abstract factory method pattern when a simple function would suffice.

“Don’t add functionality until you actually need it.”

Avoid building features “just in case” they might be useful in the future. Speculative generality adds complexity, increases maintenance burden, and often turns out to be wrong anyway. Build what is necessary now; refactor and extend when the need arises.

Violated by: Building a complex plugin system on day one for a simple script, anticipating future extensibility that never materializes.


Separation of Concerns is a broad design principle that states that a software system should be divided into distinct sections, where each section addresses a separate concern (a specific aspect of the program’s functionality).

Modern web frameworks embody this principle naturally:

  • Model: Data and business logic (the “what”)
  • View: Presentation/UI (the “how it looks”)
  • Controller: Request handling (the “how it connects”)

This separation means a designer can update the View without touching business logic, and a backend developer can change data models without affecting the UI.