9.2 — Design Principles: Quick Revision
Compact cheat sheet. Print-friendly.
How to use this material:
- Scan before an interview or exam.
- Use as a quick reference during code reviews.
- If anything is unclear, refer to the full sub-topic file.
- Covers ALL key concepts from 9.2.a through 9.2.d.
9.2.a — SOLID Principles
| Principle | Full Name | One-Liner | Violation Smell |
|---|
| S | Single Responsibility | One class = one reason to change | God classes, "and" in class descriptions |
| O | Open/Closed | Extend, don't modify | Growing switch/if-else on types |
| L | Liskov Substitution | Subtypes must honor parent contracts | throw new Error('Not supported') in overrides |
| I | Interface Segregation | Small, focused interfaces | Empty/no-op method implementations |
| D | Dependency Inversion | Depend on abstractions, not concretes | new ConcreteClass() inside business logic |
SRP Quick Check
Ask: "Who would request changes to this class?"
Multiple stakeholders → Split the class.
OCP Pattern
LSP Rules
- Preconditions: can't be strengthened in subtypes
- Postconditions: can't be weakened in subtypes
- Invariants: must be preserved in subtypes
- No surprise exceptions in subtypes
DIP Pattern
High-level → Interface ← Low-level
(not: High-level → Low-level)
SOLID in Express
| Layer | Responsibility | Principle |
|---|
| Middleware | Cross-cutting (auth, logging, CORS) | OCP |
| Controller | HTTP parsing + response formatting | SRP |
| Service | Business logic + orchestration | SRP + DIP |
| Repository | Data access | SRP + DIP |
9.2.b — DRY and Other Principles
DRY (Don't Repeat Yourself)
DRY = same KNOWLEDGE in one place (not same CODE)
Identical code, different purposes → NOT a violation (accidental duplication)
Different code, same business rule → IS a violation (true duplication)
Rule of Three: abstract on the 3rd occurrence, not the 2nd
KISS (Keep It Simple, Stupid)
Default to the simplest solution that works.
Cleverness is a liability in team codebases.
Use existing libraries before building your own.
YAGNI (You Ain't Gonna Need It)
Build for today's requirements, not tomorrow's guesses.
Applies to: features, abstractions, hypothetical scenarios
Does NOT apply to: tests, error handling, security
Composition over Inheritance
| Use Inheritance | Use Composition |
|---|
| Genuine is-a relationship | Has-a or uses-a relationship |
| Shallow hierarchy (2-3 levels) | Need to combine behaviors |
| Extending framework classes | Building domain models |
class Notifier {
private channels: NotificationChannel[] = [];
addChannel(c: NotificationChannel) { this.channels.push(c); }
async notifyAll(msg: string) {
await Promise.all(this.channels.map(c => c.send(msg)));
}
}
Law of Demeter
Only talk to:
1. this (own methods)
2. Parameters
3. Objects you create
4. Your direct properties
BAD: order.getCustomer().getAddress().getCity()
GOOD: order.getDeliveryCity()
Exceptions: data objects, fluent APIs, functional chains
Separation of Concerns
Frontend: View | State | Routing
API: Routes | Middleware | Validation
Backend: Services | Repositories | External APIs
Over-Engineering vs Under-Engineering
| Factor | Lean Simple | Lean Architecture |
|---|
| Team size | 1-2 devs | 5+ devs |
| Lifespan | Script / prototype | Multi-year product |
| Change frequency | Rarely | Weekly |
| Domain complexity | CRUD | Complex rules |
9.2.c — Design Patterns
Three Categories
Creational = How do I MAKE things? (Factory, Singleton, Builder)
Structural = How do I CONNECT things? (Adapter, Decorator, Facade, Proxy)
Behavioral = How do things TALK? (Observer, Strategy, Command, Chain)
Most-Used Patterns in JS/TS
| Pattern | JS/TS Mechanism | Example |
|---|
| Observer | EventEmitter, DOM events | emitter.on('event', handler) |
| Strategy | Closures, function objects | array.sort(comparator) |
| Singleton | Module cache | export const config = {...} |
| Iterator | for...of, generators | Symbol.iterator |
| Decorator | HOFs, TS decorators | withAuth(handler) |
| Factory | Functions returning objects | createConnection(type) |
| Proxy | Native Proxy object | new Proxy(target, handler) |
| Chain of Resp. | Express middleware | app.use(a).use(b).use(c) |
Pattern Template
Name → Problem → Context → Solution → Consequences → Related Patterns
When to Use / Skip
Use when: Real, recurring problem + pattern fits + complexity justified
Skip when: First/second occurrence + simple solution works + team won't recognize it
Rule of Three: 1st = write simple, 2nd = note it, 3rd = apply pattern
Common Anti-Patterns
| Anti-Pattern | What It Is | Fix |
|---|
| God Object | One class does everything | SRP: split into focused classes |
| Spaghetti Code | No structure, tangled logic | SoC: modularize |
| Golden Hammer | One tool for everything | Right tool for the job |
| Lava Flow | Dead code nobody removes | Delete it; use git |
| Premature Optimization | Optimize before measuring | Profile first |
9.2.d — Writing Extensible Code
Extension Spectrum
Hardcoded → Configurable → Parameterized → Pluggable → Fully Extensible
↑ Scripts ↑ Most apps ↑ Frameworks
Plugin Architecture
interface Plugin {
name: string;
version: string;
init(app: Application): void;
destroy?(): void;
}
pluginManager.register(authPlugin);
pluginManager.register(loggingPlugin);
Strategy Pattern
interface Strategy { execute(data: any): Result; }
class Engine {
private strategies = new Map<string, Strategy>();
register(name: string, s: Strategy) { this.strategies.set(name, s); }
run(name: string, data: any) { return this.strategies.get(name)!.execute(data); }
}
Feature Flags
Types: Release (days) | Experiment (weeks) | Ops (permanent) | Permission (permanent)
Capabilities: Global toggle | % rollout | User targeting | Group targeting
Best practices: Default OFF | Log evaluations | Clean up after rollout
Dependency Injection
| Type | When to Use |
|---|
| Constructor | Required dependencies (most common) |
| Setter | Optional or changeable dependencies |
| Interface | Framework-managed injection |
const db = new PostgresDatabase(url);
const repo = new UserRepository(db);
const service = new UserService(repo, emailer);
const controller = new UserController(service);
const mockDb = new InMemoryDatabase();
const testRepo = new UserRepository(mockDb);
const testService = new UserService(testRepo, mockEmailer);
Configuration over Code
Code (hardcoded) → Env vars → Config files → Database → Admin panel
Less flexible ──────────────────────────────────── More dynamic
Event-Driven Extensibility
events.emit('order:placed', order);
events.on('order:placed', sendConfirmationEmail);
events.on('order:placed', updateInventory);
events.on('order:placed', trackAnalytics);
Master Checklist — Code Review Questions
- Does each class/function have a single, clear responsibility?
- Can I add new behavior without modifying existing code?
- Are subtypes safe substitutes for their parents?
- Are interfaces small and focused?
- Do high-level modules depend on abstractions?
- Is knowledge represented in exactly one place?
- Is this the simplest solution that works?
- Am I building something I actually need right now?
- Am I using composition instead of deep inheritance?
- Can I test this in isolation?
Return to Overview