Simplicity First
Core Principle
- The right amount of complexity is the minimum needed for the current task
- Make it work, make it clear, then — only if needed — make it fast
- Every abstraction has a cost; justify it with a real (not hypothetical) need
Gall’s Law
- A complex system that works always evolved from a simple system that worked — never design a complex system from scratch
- Build the simplest version that solves the problem, then iterate — complexity is earned, not planned
YAGNI — You Aren’t Gonna Need It
- Solve the current problem, not imagined future ones
- Do not add configuration, feature flags, or extension points until a second use case exists
- Delete speculative code — version control remembers it if you ever need it back
BAD: Building a plugin system for an app that has one integration GOOD: A direct implementation now; extract a plugin system when the third integration arrives
Inline Over Abstraction
- Three similar lines of code are better than a premature utility function
- Extract a helper only when the same logic appears in three or more places
- Avoid wrapping standard library or framework APIs — callers already know them
BAD: wrapFetch(url) that adds nothing over fetch(url)
GOOD: Using fetch(url) directly until retry/timeout logic is genuinely needed
Flat Over Nested
- Prefer flat directory structures for small projects — deep hierarchies obscure simple codebases
- One file per concern is fine; one directory per concern is premature until the concern grows
- Avoid creating folders with a single file inside
Boring Technology
- Choose well-understood, battle-tested technology over novel alternatives — every new technology consumes an “innovation token”
- Limit the number of novel technologies per project to one or two — the rest should be boring and predictable
- New does not mean better — evaluate technology by its failure modes, not its feature list
Minimal Dependencies
- Prefer the standard library over third-party packages for simple tasks
- Every dependency is an upgrade burden, a security surface, and a build-time cost
- Before adding a package, ask: can I write this in under 50 lines?
Skip Ceremony
- Do not introduce design patterns (factories, observers, strategies, DI containers) unless the problem demands them
- Do not create interfaces with a single implementation — that is speculative abstraction
- Do not write abstractions for “testability” that make the code harder to read
BAD: IUserRepository interface with only UserRepository implementing it
GOOD: UserRepository class directly — extract the interface when a second implementation exists
No Premature Optimization
- Write clear code first; optimize only when profiling shows a bottleneck
- Avoid caching until you prove the computation is expensive
- Avoid parallelism until you prove the sequential version is too slow
- Algorithmic simplicity (O(n) is usually fine) beats clever O(log n) with complex setup
Complexity Budget
- Distinguish essential complexity (inherent to the problem) from incidental complexity (artifacts of poor design) — eliminate incidental complexity ruthlessly
- Track cognitive complexity with linter rules — code that scores high is a refactoring candidate
- Simplification is leverage: reducing complexity pays compounding dividends in fewer bugs, faster onboarding, and easier changes
Keep It Readable
- Prefer explicit over clever — the next reader should understand the code without comments
- Use descriptive variable names even if they are longer
- Avoid ternary chains, nested conditionals, and one-liner hacks
- If a function needs a paragraph of explanation, it is too complex — simplify the code, not the comment