← Back to Design Thinking

Design Thinking

Anti-Patterns & What Not To Do

Knowing what not to do improves your design instincts faster. Learn to avoid over-engineering, under-engineering, premature optimization, and other common mistakes.

Intermediate20 min read

Knowing what not to do improves your design instincts faster.


Why Anti-Patterns Matter

Learning from mistakes (yours and others') helps you:

  • Avoid common pitfalls: Don't repeat mistakes
  • Make better decisions: Know what to avoid
  • Save time: Don't waste time on wrong approaches
  • Build better systems: Learn from failures

Anti-Pattern 1: Over-Engineering

What It Is

Building more than you need:

  • Complex architecture for simple problems
  • Microservices for MVP
  • Distributed systems when single server works
  • Premature optimization

Example: URL Shortener MVP

Over-Engineered:

  • Microservices (URL service, redirect service, analytics service)
  • Kubernetes cluster
  • Multiple databases (PostgreSQL, Redis, Cassandra)
  • Message queue (Kafka)
  • CDN, load balancers

Problem:

  • Too complex for MVP
  • Hard to maintain
  • Slow to develop
  • Unnecessary infrastructure costs

Right Approach:

  • Single service (monolith)
  • Single database (PostgreSQL)
  • Simple cache (Redis)
  • Add complexity when needed

How to Avoid

  1. Start simple: Build MVP first
  2. Add complexity when needed: Scale when you have evidence
  3. Question every component: Do you really need it?
  4. Measure before optimizing: Don't optimize prematurely

Anti-Pattern 2: Under-Engineering

What It Is

Building less than you need:

  • Single server for high-scale system
  • No caching for read-heavy workloads
  • No monitoring for production systems
  • No failure handling

Example: Social Media Feed

Under-Engineered:

  • Single database
  • No caching
  • No load balancing
  • No monitoring
  • No failure handling

Problem:

  • System breaks at scale
  • No visibility into issues
  • Can't handle failures
  • Poor user experience

Right Approach:

  • Database with read replicas
  • Cache for hot data
  • Load balancer for API
  • Monitoring and alerting
  • Failure handling (retries, fallbacks)

How to Avoid

  1. Design for scale: Think about 10x, 100x scale
  2. Add essential components: Caching, monitoring, failure handling
  3. Plan for growth: Design with scale in mind
  4. Don't skip basics: Monitoring, logging, error handling

Anti-Pattern 3: Redesigning Without Constraints

What It Is

Redesigning a system without understanding constraints:

  • Ignoring existing infrastructure
  • Ignoring team expertise
  • Ignoring budget/timeline
  • Ignoring business requirements

Example: Database Migration

Wrong Approach:

  • "Let's migrate from MySQL to PostgreSQL because it's better"
  • No analysis of current system
  • No consideration of migration cost
  • No understanding of why MySQL was chosen

Problem:

  • High migration cost
  • Risk of data loss
  • Team doesn't know PostgreSQL
  • No clear benefit

Right Approach:

  • Understand why MySQL was chosen
  • Analyze current system performance
  • Identify actual problems
  • Consider migration cost vs benefit
  • Make informed decision

How to Avoid

  1. Understand constraints: Infrastructure, team, budget, timeline
  2. Analyze current system: What works? What doesn't?
  3. Identify real problems: Don't solve imaginary problems
  4. Consider alternatives: Is redesign necessary?
  5. Make informed decisions: Based on data, not assumptions

Anti-Pattern 4: Premature Optimization

What It Is

Optimizing before you have evidence:

  • Optimizing for scale you don't have
  • Optimizing for problems that don't exist
  • Optimizing without measuring
  • Optimizing the wrong thing

Example: Caching Strategy

Premature Optimization:

  • "Let's add Redis cache everywhere"
  • No measurement of database load
  • No analysis of cache hit rates
  • No understanding of actual bottlenecks

Problem:

  • Unnecessary complexity
  • Wasted resources
  • Harder to maintain
  • May not solve actual problems

Right Approach:

  • Measure database load first
  • Identify actual bottlenecks
  • Add cache only where needed
  • Measure cache effectiveness
  • Optimize based on data

How to Avoid

  1. Measure first: Understand current performance
  2. Identify bottlenecks: Find actual problems
  3. Optimize based on data: Not assumptions
  4. Start simple: Add complexity when needed
  5. Question optimizations: Do you really need it?

Anti-Pattern 5: Ignoring Failure Scenarios

What It Is

Designing only for happy path:

  • No error handling
  • No retry logic
  • No fallback mechanisms
  • No monitoring

Example: Payment System

Ignoring Failures:

  • Payment service calls external API
  • No retry on failure
  • No timeout handling
  • No fallback mechanism
  • No monitoring

Problem:

  • Payment fails silently
  • Users lose money
  • No visibility into issues
  • Poor user experience

Right Approach:

  • Retry with exponential backoff
  • Timeout handling
  • Fallback to alternative payment method
  • Monitoring and alerting
  • Error logging

How to Avoid

  1. Design for failure: What can go wrong?
  2. Add error handling: Retries, timeouts, fallbacks
  3. Monitor failures: Track error rates, alert on issues
  4. Test failure scenarios: Chaos engineering, failure injection
  5. Plan for recovery: How do you recover from failures?

Anti-Pattern 6: Technology-Driven Design

What It Is

Choosing technology first, then designing around it:

  • "Let's use microservices because they're popular"
  • "Let's use NoSQL because it's modern"
  • "Let's use Kubernetes because everyone uses it"

Example: Database Choice

Technology-Driven:

  • "Let's use MongoDB because it's NoSQL and modern"
  • No analysis of requirements
  • No consideration of trade-offs
  • No understanding of use case

Problem:

  • Wrong tool for the job
  • Doesn't solve actual problems
  • Adds unnecessary complexity
  • Hard to maintain

Right Approach:

  • Understand requirements first
  • Analyze data structure (structured vs unstructured)
  • Consider trade-offs (consistency, scale, queries)
  • Choose technology based on requirements

How to Avoid

  1. Requirements first: Understand what you're building
  2. Analyze trade-offs: Pros and cons of each technology
  3. Choose based on needs: Not popularity
  4. Question technology choices: Do you really need it?
  5. Start simple: Use familiar technologies first

Anti-Pattern 7: No Monitoring or Observability

What It Is

Building systems without visibility:

  • No metrics
  • No logging
  • No alerting
  • No dashboards

Example: API Service

No Observability:

  • API service with no metrics
  • No error logging
  • No performance monitoring
  • No alerting

Problem:

  • Can't detect issues
  • Can't debug problems
  • Can't optimize performance
  • Poor user experience

Right Approach:

  • Metrics (latency, throughput, error rate)
  • Logging (structured logs, error logs)
  • Alerting (set up alerts for critical issues)
  • Dashboards (visualize system health)

How to Avoid

  1. Add monitoring from day 1: Don't add later
  2. Track key metrics: Latency, throughput, error rate
  3. Set up alerting: Alert on critical issues
  4. Use dashboards: Visualize system health
  5. Review regularly: Monitor and optimize

Anti-Pattern 8: Tight Coupling

What It Is

Services/components depend too much on each other:

  • Direct database access from multiple services
  • Hard-coded dependencies
  • Synchronous calls everywhere
  • Shared state

Example: Microservices

Tight Coupling:

  • Order service directly accesses user database
  • Services call each other synchronously
  • Shared database between services
  • Hard-coded service URLs

Problem:

  • Services can't scale independently
  • Failure in one service affects others
  • Hard to test
  • Hard to deploy independently

Right Approach:

  • Each service owns its data
  • Services communicate via APIs or events
  • Asynchronous communication where possible
  • Service discovery instead of hard-coded URLs

How to Avoid

  1. Loose coupling: Services should be independent
  2. API boundaries: Communicate via APIs
  3. Event-driven: Use events for decoupling
  4. Own your data: Each service owns its data
  5. Service discovery: Don't hard-code service URLs

Thinking Aloud Like a Senior Engineer

Let me walk you through how I'd actually approach avoiding anti-patterns when designing a system. This is the real-time reasoning that happens when you're trying to build something right.

Problem: "Design a URL shortener. We need to handle 100M URLs/day and 10B redirects/day."

My first instinct: "I'll use microservices! That's what everyone does for scale, right? URL service, redirect service, analytics service, each in its own microservice with its own database."

But wait—that's over-engineering. Let me think about this more carefully.

What's the actual requirement? 100M URLs/day, 10B redirects/day. That's high scale, but do I need microservices from day 1?

My next thought: "Actually, I could start with a monolith. It's simpler, faster to develop, and I can always split later if needed."

But that might be under-engineering—what if I can't split later? What if the monolith becomes too complex?

Let me think about the actual constraints:

  • Scale: 100M URLs/day, 10B redirects/day
  • Team: How many engineers? If it's just me or a small team, microservices add too much complexity.
  • Timeline: When do we need to launch? If it's soon, monolith is faster.

Assuming: Small team (3 engineers), need to launch in 3 months.

I'm choosing monolith first: "We'll start with a monolith because:

  • Small team can move faster
  • Simpler to develop and debug
  • We can always split later if needed
  • This is the trade-off I'm accepting: simpler now, potential refactoring later"

But I need to avoid under-engineering too. What are the essentials?

  • Database (obviously)
  • Cache for redirects (10B redirects/day needs caching)
  • Load balancer (can't handle 10B redirects on single server)
  • Monitoring (need to know if system is healthy)

I'm not skipping these: "Even for MVP, I need caching and monitoring. These aren't optional—they're essential for a production system."

Now, about the database choice: "Should I use SQL or NoSQL?"

My first instinct: "PostgreSQL is solid, has ACID, everyone knows it."

But wait—100M URLs/day means billions of URLs stored. Can PostgreSQL handle that? It can, but it's harder to scale horizontally.

I'm considering NoSQL: "Cassandra or DynamoDB could handle the scale better. But do I need it now?"

Actually, let me measure first: "I'll start with PostgreSQL, measure the performance, and migrate to NoSQL if needed. This avoids premature optimization."

This is the trade-off I'm making: Start with familiar technology (PostgreSQL), accept potential migration later, but avoid premature optimization.

What about caching? "I definitely need Redis for redirects. 10B redirects/day means I can't hit the database for every redirect."

My first instinct: "I'll cache everything! All URLs in Redis!"

But that's premature optimization: "I don't know the access patterns yet. Let me start with cache-aside pattern, cache hot URLs, measure hit rates, then optimize."

This is how I avoid anti-patterns: I question every decision, measure before optimizing, start simple, and add complexity only when I have evidence I need it.


How a Senior Engineer Avoids Anti-Patterns

A senior engineer:

  1. Starts simple: Builds MVP first, adds complexity when needed
  2. Measures before optimizing: Understands current performance
  3. Designs for failure: Handles errors, retries, fallbacks
  4. Chooses technology based on requirements: Not popularity
  5. Adds monitoring from day 1: Visibility into system health
  6. Designs for loose coupling: Independent, scalable services
  7. Questions everything: Do you really need it?

Best Practices

  1. Start simple: Build MVP first, add complexity when needed
  2. Measure before optimizing: Understand current performance
  3. Design for failure: Handle errors, retries, fallbacks
  4. Choose technology based on requirements: Not popularity
  5. Add monitoring from day 1: Visibility into system health
  6. Design for loose coupling: Independent, scalable services
  7. Question everything: Do you really need it?
  8. Learn from mistakes: Study failures, avoid repeating them

Common Interview Questions

Beginner

Q: What is over-engineering and how do you avoid it?

A: Over-engineering is building more than you need (complex architecture for simple problems). To avoid it:

  • Start simple (build MVP first)
  • Add complexity when needed (scale when you have evidence)
  • Question every component (do you really need it?)
  • Measure before optimizing (don't optimize prematurely)

Intermediate

Q: How do you decide between over-engineering and under-engineering?

A: I consider:

  • Requirements: What are you actually building?
  • Scale: What scale do you need to handle?
  • Constraints: Team size, budget, timeline
  • Evidence: Do you have data showing you need complexity?

I start simple (MVP), then add complexity based on evidence (measure, then optimize). I avoid both extremes by designing for current needs while planning for growth.


Senior

Q: You're asked to redesign a system. How do you avoid common anti-patterns?

A: I follow a systematic approach:

  1. Understand constraints: Infrastructure, team, budget, timeline
  2. Analyze current system: What works? What doesn't? Why?
  3. Identify real problems: Don't solve imaginary problems
  4. Measure first: Understand current performance before optimizing
  5. Design for failure: Handle errors, retries, fallbacks
  6. Choose technology based on requirements: Not popularity
  7. Add monitoring: Visibility into system health
  8. Start simple: Build MVP, evolve as needed

I avoid:

  • Over-engineering (building more than needed)
  • Under-engineering (skipping essentials)
  • Premature optimization (optimizing without data)
  • Technology-driven design (choosing tech first)
  • Ignoring failures (designing only for happy path)

Summary

Anti-patterns are common mistakes to avoid:

  • Over-Engineering: Building more than you need
  • Under-Engineering: Building less than you need
  • Redesigning Without Constraints: Ignoring existing constraints
  • Premature Optimization: Optimizing before measuring
  • Ignoring Failure Scenarios: Designing only for happy path
  • Technology-Driven Design: Choosing technology first
  • No Monitoring: Building without visibility
  • Tight Coupling: Services depend too much on each other

Key takeaways:

  • Start simple, add complexity when needed
  • Measure before optimizing
  • Design for failure
  • Choose technology based on requirements
  • Add monitoring from day 1
  • Design for loose coupling
  • Question everything
  • Learn from mistakes

FAQs

Q: How do I know if I'm over-engineering?

A: You're over-engineering if:

  • Building complex architecture for simple problems
  • Adding components you don't need
  • Optimizing for scale you don't have
  • Can't explain why you need each component

Q: How do I know if I'm under-engineering?

A: You're under-engineering if:

  • System breaks at scale
  • No monitoring or error handling
  • No caching for read-heavy workloads
  • Can't handle failures

Q: Should I always start simple?

A: Usually yes, but consider:

  • Requirements: What scale do you need?
  • Constraints: Team size, budget, timeline
  • Evidence: Do you have data showing you need complexity?

Start simple, but design with scale in mind.

Q: How do I avoid premature optimization?

A:

  • Measure first (understand current performance)
  • Identify bottlenecks (find actual problems)
  • Optimize based on data (not assumptions)
  • Start simple (add complexity when needed)
  • Question optimizations (do you really need it?)

Q: What if I'm not sure if I need a component?

A: Ask:

  • What problem does it solve?
  • Do you have evidence you need it?
  • What's the cost of adding it?
  • What's the cost of not adding it?

If unsure, start without it, add when you have evidence.

Q: How do I learn to avoid anti-patterns?

A:

  • Study failures (learn from mistakes)
  • Practice designing systems
  • Get feedback from peers
  • Read case studies (how companies avoided mistakes)
  • Question your decisions (do you really need it?)

Q: Are anti-patterns always bad?

A: Not always. Sometimes what seems like an anti-pattern is the right choice for your context. The key is to make informed decisions based on requirements, not blindly following or avoiding patterns.

Keep exploring

Design thinking works best when combined with practice. Explore more topics or apply what you've learned in our system design practice platform.