Every architecture decision you make in the first six months of a project will still be there in year three. I've worked on enough codebases to know this with certainty.
The Compounding Problem
When a system is small, almost anything works. A monolith is fine. A big switch
statement is fine. Storing config in environment variables is fine.
The problem is that these choices don't disappear — they become load-bearing walls.
What Actually Compounds
After auditing dozens of codebases, I've found three categories of decisions that compound the hardest:
1. Data model decisions
Your data model is the skeleton of your system. Feature pressure will cause you to add columns, nullable fields, and polymorphic associations. These are survivable. But the shape of your core entities — how you model users, how you handle multi-tenancy, how you represent relationships — almost never changes.
2. Authentication and identity
Auth touches everything. The decision to use sessions vs. JWTs, to roll your own vs. use a third-party, will echo through your codebase for years.
3. Synchronous vs. asynchronous processing
Starting synchronous is simpler. But retrofitting async (workers, queues, retries) into a sync-first system is genuinely painful.
The Heuristic I Use
Before committing to a pattern, I ask: "If this project gets 10x bigger, does this choice get better or worse?"
- A well-defined API boundary? Gets better — contracts stay stable.
- Shared mutable database tables between services? Gets worse — coupling increases.
- Centralized logging and tracing? Gets better — observability value rises.
- Implicit conventions over documented contracts? Gets dramatically worse.
Closing Thought
You won't get it all right. But the engineers who build systems that age well aren't necessarily smarter — they're just more deliberate about which decisions they're making permanent.
Treat today's architecture like a letter to future-you. Make it legible.
Jason Lima
Software engineer. I write about building systems, the craft of code, and lessons learned from shipping real products. Say hello →
Related reading
On Writing Code That Explains Itself
Self-documenting code is not about avoiding comments — it's about making the code itself tell the story clearly enough that comments become optional.
TypeScript Patterns I Actually Reach For
Not the clever tricks — the boring, readable patterns that appeared again and again across every large TypeScript codebase I've worked in.