Back to all ideas
Framework

Progressive Complexity Principle

Build systems that start simple and allow complexity only when needed. A design philosophy for creating software that scales with requirements, not ahead of them.

System DesignArchitectureSimplicity

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:

  1. What problem does this solve?

    • Must be a current, measured problem
    • Not a hypothetical future problem
  2. What's the simplest solution?

    • Have you exhausted simpler alternatives?
    • Can you solve this with existing tools?
  3. What's the cost?

    • Development time
    • Operational overhead
    • Team cognitive load
  4. 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.


  • "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.

AM

Abhinav Mahajan

AI Product & Engineering Leader

Building AI systems that work in production. These frameworks come from real experience shipping enterprise AI products.

💡 Apply This Framework

Find This Framework Useful?

I'd love to hear how you've applied it or discuss related ideas. Let's explore how these principles apply to your specific context.