The Core Idea
Start with the simplest solution that could possibly work. Add complexity only when you have evidence it's needed.
This sounds obvious, but most systems are over-engineered from day one. The Progressive Complexity Principle provides a framework for building systems that grow in sophistication as requirements demand.
The Ladder of Complexity
Think of system design as a ladder where each rung adds capability—and cost.
Rung 1: Simple & Direct
When to use: MVP, prototypes, low-scale systems
Example: Data Storage
- SQLite in a single file
- No replication, no caching
- Direct queries from application code
Handles: Thousands of users, simple queries
Move up when: Database becomes a bottleneck or you need multi-region
Rung 2: Optimized Monolith
When to use: Proven product, growing user base
Example: Data Storage
- Managed PostgreSQL (RDS, Cloud SQL)
- Read replicas for scaling reads
- Connection pooling
- Simple caching layer (Redis)
Handles: Hundreds of thousands of users
Move up when: Vertical scaling maxes out or teams can't deploy independently
Rung 3: Distributed Services
When to use: Large scale, multiple teams
Example: Data Storage
- Sharded databases
- Separate read/write paths
- Event-driven architecture
- Dedicated caching layer
Handles: Millions of users, complex workflows
Move up when: Regional distribution needed or domain complexity demands it
Rung 4: Hyper-Scale
When to use: Global products, extreme scale
Example: Data Storage
- Multi-region active-active
- Custom distributed databases
- CQRS pattern
- Edge caching (CDN)
- Eventual consistency
Handles: Billions of users, global presence
Move up when: You're in top 0.1% of scale (you'll know)
The Rules
Rule 1: Start at the Lowest Rung
Bad:
"We might need to scale to millions of users someday, so let's build microservices and use Kubernetes from day one."
Good:
"We have 100 users. Let's use SQLite and a single server. We'll refactor when we hit limits."
Why: Every rung adds operational complexity, more failure modes, and slower iteration. Pay this cost only when you must.
Rule 2: Evidence-Based Climbing
Only move up the ladder when you have concrete evidence (metrics, not hunches) that you need to.
Evidence that justifies complexity:
- ✅ Database CPU consistently >80%
- ✅ Response time P95 exceeds SLA
- ✅ Engineers blocked waiting for deployments
- ✅ Outages caused by lack of redundancy
Not evidence:
- ❌ "This is what Google does"
- ❌ "It will be easier to scale later"
- ❌ "We might need this feature someday"
Rule 3: Climb One Rung at a Time
Don't skip rungs.
Bad: Jump from SQLite to sharded Cassandra cluster
Good: SQLite → PostgreSQL → PostgreSQL + replicas → Sharded PostgreSQL
Why: Each rung teaches you lessons needed for the next. Skipping rungs means learning the hard way in production.
Rule 4: Support Climbing Down
Build systems that allow you to simplify if you over-engineered.
Example: Use abstractions that work at multiple rungs
# Works with SQLite or PostgreSQL
def get_user(user_id):
return db.query("SELECT * FROM users WHERE id = ?", user_id)
Not:
# Locked into specific database
def get_user(user_id):
return cassandra_cluster.execute_async_with_retry(
prepared_statement, consistency_level=QUORUM
)
Real-World Application
Case Study 1: Instagram (Done Right)
Launch (2010):
- Single PostgreSQL database
- Django monolith on a few servers
- Grew to 1M users in 2 months
Growth (2011-2012):
- Added read replicas
- Implemented caching with memcached
- Sharded photos separately from users
Scale (2012+):
- Migrated to Cassandra for photos
- Built custom infrastructure
- Multi-region deployment
Key insight: They started simple and added complexity based on real pain points.
Case Study 2: Over-Engineering (Common Mistake)
A startup I consulted for:
Their initial architecture:
- Kubernetes cluster (3 environments)
- 15 microservices
- Event-driven with Kafka
- Service mesh for observability
- 3 databases (PostgreSQL, MongoDB, Redis)
Their user count: 0 (pre-launch)
Result:
- 6 months to build basic features
- $8K/month infrastructure costs
- Team spent more time on DevOps than product
- Ran out of funding before product-market fit
What they should have done:
- Start with a monolith on Heroku ($25/month)
- Ship fast, learn from users
- Add complexity when needed
Decision Framework
Ask these questions before adding complexity:
-
What problem does this solve?
- Must be a current, measured problem
- Not a hypothetical future problem
-
What's the simplest solution?
- Have you exhausted simpler alternatives?
- Can you solve this with existing tools?
-
What's the cost?
- Development time
- Operational overhead
- Team cognitive load
-
What's the evidence threshold?
- What metrics will tell you when you need this?
- Define the trigger before building
Common Objections
"It's harder to refactor later"
Actually, refactoring with real usage data is easier than guessing requirements upfront.
"We'll outgrow this quickly"
Most products don't. If you do, that's a good problem—you'll have resources to solve it.
"Our engineers want to use cool tech"
Use boring technology for the core product. Experiment on side projects or internal tools.
"Investors expect scalable architecture"
Investors expect revenue. Premature optimization is waste they're paying for.
The Balance
This isn't an argument for always being simple—it's an argument for being appropriately complex.
- Don't use SQLite for a database serving millions of requests per second
- Don't use Kubernetes for a WordPress blog
- Don't build distributed systems for single-team products
The goal: Match complexity to requirements, not aspirations.
Conclusion
The Progressive Complexity Principle is about discipline:
- Start simple: Default to the lowest rung
- Climb deliberately: Only when evidence demands it
- Climb slowly: One rung at a time
- Stay flexible: Be able to climb down if needed
Remember: Complexity is expensive. Make it pay for itself.
Related Reading
- "Boring Technology" by Dan McKinley
- "Simple Made Easy" by Rich Hickey
- "The Grug Brained Developer" (grug-brained)
What's your default complexity level? Are you over-engineering or under-engineering? I'd love to hear your experiences.