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.
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
- 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
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
- Design for scale: Think about 10x, 100x scale
- Add essential components: Caching, monitoring, failure handling
- Plan for growth: Design with scale in mind
- 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
- Understand constraints: Infrastructure, team, budget, timeline
- Analyze current system: What works? What doesn't?
- Identify real problems: Don't solve imaginary problems
- Consider alternatives: Is redesign necessary?
- 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
- 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?
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
- Design for failure: What can go wrong?
- Add error handling: Retries, timeouts, fallbacks
- Monitor failures: Track error rates, alert on issues
- Test failure scenarios: Chaos engineering, failure injection
- 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
- Requirements first: Understand what you're building
- Analyze trade-offs: Pros and cons of each technology
- Choose based on needs: Not popularity
- Question technology choices: Do you really need it?
- 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
- Add monitoring from day 1: Don't add later
- Track key metrics: Latency, throughput, error rate
- Set up alerting: Alert on critical issues
- Use dashboards: Visualize system health
- 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
- Loose coupling: Services should be independent
- API boundaries: Communicate via APIs
- Event-driven: Use events for decoupling
- Own your data: Each service owns its data
- 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:
- Starts simple: Builds MVP first, adds complexity when needed
- Measures before optimizing: Understands current performance
- Designs for failure: Handles errors, retries, fallbacks
- Chooses technology based on requirements: Not popularity
- Adds monitoring from day 1: Visibility into system health
- Designs for loose coupling: Independent, scalable services
- Questions everything: Do you really need it?
Best Practices
- Start simple: Build MVP first, add complexity when needed
- Measure before optimizing: Understand current performance
- Design for failure: Handle errors, retries, fallbacks
- Choose technology based on requirements: Not popularity
- Add monitoring from day 1: Visibility into system health
- Design for loose coupling: Independent, scalable services
- Question everything: Do you really need it?
- 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:
- Understand constraints: Infrastructure, team, budget, timeline
- Analyze current system: What works? What doesn't? Why?
- Identify real problems: Don't solve imaginary problems
- Measure first: Understand current performance before optimizing
- Design for failure: Handle errors, retries, fallbacks
- Choose technology based on requirements: Not popularity
- Add monitoring: Visibility into system health
- 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.