Microservices vs Monolithic Architecture: When to Use Which

Master microservices vs monolithic architecture for system design interviews. Learn when to use each with real examples from Netflix, Uber, Amazon, and common migration patterns

πŸ“… Published: January 18, 2025 ✏️ Updated: February 25, 2025 By Ojaswi Athghara
#microservices #monolithic #architecture #system-design #scalability

Microservices vs Monolithic Architecture: When to Use Which

Building a House vs Building a City

Imagine you're building a house:

  • One blueprint
  • One foundation
  • One roof
  • Everything connected
  • Fix the plumbing? Access the whole structure.

This is a monolith.

Now imagine building a city:

  • Independent neighborhoods
  • Separate water systems
  • Individual power grids
  • Fix one neighborhood's plumbing? Others unaffected.

This is microservices.

Both architectures work. The question is: Are you building a house, or a city?

In this guide, I'll break down monolithic vs microservices architecture, when to use each, how companies like Netflix and Amazon made the transition, and the pitfalls to avoid. Let's dive in.


What Is Monolithic Architecture?

Monolithic = All code in a single, unified application

Structure:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Monolithic Application    β”‚
β”‚                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   User Management     β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   Product Catalog     β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   Order Processing    β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   Payment Service     β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        ↓
  [Single Database]

All features:

  • Deployed together
  • Share same codebase
  • Run in same process
  • Access same database

Example: E-Commerce Monolith

Single application:

# app.py
from flask import Flask, request

app = Flask(__name__)

# User management
@app.route('/users', methods=['POST'])
def create_user():
    # Insert user into database
    return {'status': 'user created'}

# Product catalog
@app.route('/products', methods=['GET'])
def get_products():
    # Fetch products from database
    return {'products': [...]}

# Order processing
@app.route('/orders', methods=['POST'])
def create_order():
    # Create order, reduce inventory, process payment
    return {'status': 'order placed'}

# Payment processing
@app.route('/payments', methods=['POST'])
def process_payment():
    # Charge credit card
    return {'status': 'payment processed'}

if __name__ == '__main__':
    app.run()

Everything in one file (or one project), deployed as single unit.


Advantages of Monolithic Architecture

βœ… 1. Simple to Develop

Start coding immediately:

$ git clone repo
$ pip install requirements.txt
$ python app.py
# Application running!

No complex infrastructure, no service orchestration.

βœ… 2. Easy to Test

One application = one test suite:

$ pytest
# Test entire application

No need to mock 20 microservices.

βœ… 3. Simple to Deploy

$ docker build -t myapp .
$ docker push myapp
$ kubectl apply -f deployment.yaml
# Done!

One deployment, not 50.

βœ… 4. Better Performance (Initially)

Monolith:

# Get user and their orders (in-memory)
user = db.query("SELECT * FROM users WHERE id = 1")
orders = db.query("SELECT * FROM orders WHERE user_id = 1")
# Total: 2 database queries, < 50ms

Microservices:

# Get user (network call to User Service)
user = requests.get("http://user-service/users/1")  # 20ms

# Get orders (network call to Order Service)
orders = requests.get("http://order-service/orders?user_id=1")  # 20ms

# Total: 2 network calls, > 40ms (plus network overhead)

βœ… 5. Easier to Debug

Single codebase:

  • Stack traces show full flow
  • Logs in one place
  • One application to monitor

Disadvantages of Monolithic Architecture

❌ 1. Tight Coupling

Problem: Everything depends on everything

# In monolith
def create_order(user_id, product_id):
    user = get_user(user_id)  # User module
    product = get_product(product_id)  # Product module
    
    if product.inventory > 0:
        reduce_inventory(product_id)  # Inventory module
        charge_payment(user.payment_method)  # Payment module
        send_email(user.email)  # Email module
    
    return order

Change email module? β†’ Test entire order flow (everything's connected)

❌ 2. Hard to Scale

Scenario: Only payment processing is slow

Monolith solution: Scale entire application

Before: 1 instance
After: 10 instances (all 10 include user, product, order, payment)

Problem: Wasting resources on unused modules

Can't scale just payment service.

❌ 3. Slow Development

100 developers working on monolith:

  • Merge conflicts (everyone editing same codebase)
  • Long build times (compile entire application)
  • Testing takes hours (integration tests for everything)
  • Deploy rarely (risk breaking everything)

Real Example: Amazon in 2001

  • One monolithic codebase
  • Deploys every 11.6 seconds caused cascading failures
  • Teams blocked by each other's changes

❌ 4. Technology Lock-In

Monolith in Java:

  • All features must be Java
  • Want to use Python for ML model? Can't easily integrate
  • Stuck with Java version (upgrading risky)

❌ 5. Single Point of Failure

Bug in payment module:

Payment module crashes
    ↓
Entire application crashes
    ↓
Users can't browse products (even though product module works!)

What Is Microservices Architecture?

Microservices = Application split into small, independent services

Structure:

[User Service]       [Product Service]
       ↓                    ↓
  [User DB]            [Product DB]

[Order Service]      [Payment Service]
       ↓                    ↓
  [Order DB]           [Payment DB]

Each service:

  • Independent codebase
  • Own database
  • Deployed separately
  • Communicates via APIs (HTTP, gRPC, message queues)

Example: E-Commerce Microservices

User Service:

# user_service.py
@app.route('/users', methods=['POST'])
def create_user():
    # Insert into user database
    return {'status': 'user created'}

Product Service:

# product_service.py
@app.route('/products', methods=['GET'])
def get_products():
    # Fetch from product database
    return {'products': [...]}

Order Service:

# order_service.py
@app.route('/orders', methods=['POST'])
def create_order():
    # Call User Service
    user = requests.get(f"{USER_SERVICE_URL}/users/{user_id}")
    
    # Call Product Service
    product = requests.get(f"{PRODUCT_SERVICE_URL}/products/{product_id}")
    
    # Call Payment Service
    payment = requests.post(f"{PAYMENT_SERVICE_URL}/payments", json={...})
    
    # Create order
    return {'status': 'order placed'}

Each service:

  • Separate codebase
  • Separate repository
  • Separate deployment

Advantages of Microservices

βœ… 1. Independent Scaling

Problem: Payment processing needs more resources

Microservices solution: Scale only Payment Service

Before:
  - Payment Service: 2 instances
  - User Service: 2 instances
  - Product Service: 2 instances

After:
  - Payment Service: 10 instances ← Scaled
  - User Service: 2 instances
  - Product Service: 2 instances

Cost: Only 8 additional instances (not entire app)

βœ… 2. Technology Flexibility

Different services, different languages:

User Service: Python (Flask)
Product Service: Node.js (Express)
Payment Service: Go (high performance)
Recommendation Service: Python (scikit-learn)
Image Processing: Python (OpenCV)

Choose the best tool for each job.

βœ… 3. Faster Development

100 developers, 10 teams:

  • Team 1: User Service (own repo)
  • Team 2: Product Service (own repo)
  • ...
  • Team 10: Payment Service (own repo)

Benefits:

  • No merge conflicts between teams
  • Deploy independently (100+ deploys/day possible)
  • Faster iteration

Real Example: Netflix

  • 700+ microservices
  • Deploys thousands of times per day
  • Teams move independently

βœ… 4. Fault Isolation

Payment Service crashes:

Payment Service down ❌
    ↓
Order placement fails ❌
    ↓
But users can still:
  - Browse products βœ…
  - View their profile βœ…
  - Add to cart βœ…

(Graceful degradation: Show "Payment temporarily unavailable")

Compare to monolith: Everything crashes.

βœ… 5. Easier to Maintain

Small services:

  • User Service: 5,000 lines of code (understandable)
  • vs Monolith: 500,000 lines (cognitive overload)

New developer:

  • Monolith: 2 months to understand
  • Single microservice: 1 week

Disadvantages of Microservices

❌ 1. Increased Complexity

Monolith:

1 application
1 database
1 deployment

Microservices:

50 services
50 databases
50 deployments
Service discovery
Load balancers
API gateways
Message queues
Monitoring (50 services!)
Distributed tracing

Operational overhead is HUGE.

❌ 2. Network Latency

Monolith (in-process):

def get_user_orders(user_id):
    user = get_user(user_id)  # In-memory call (< 1ms)
    orders = get_orders(user_id)  # In-memory call (< 1ms)
    # Total: 2ms

Microservices (network calls):

def get_user_orders(user_id):
    user = requests.get(f"{USER_SERVICE}/users/{user_id}")  # 20ms
    orders = requests.get(f"{ORDER_SERVICE}/orders?user_id={user_id}")  # 20ms
    # Total: 40ms (20x slower!)

❌ 3. Data Consistency Challenges

Scenario: Create order (requires updating inventory and creating payment)

Monolith (transactions):

def create_order():
    db.begin_transaction()
    try:
        reduce_inventory()  # Same database
        create_payment()  # Same database
        db.commit()  # Atomic!
    except:
        db.rollback()  # All or nothing

Microservices (distributed transaction):

def create_order():
    # Call Inventory Service
    requests.post(f"{INVENTORY_SERVICE}/reduce", ...)  # Success
    
    # Call Payment Service
    requests.post(f"{PAYMENT_SERVICE}/charge", ...)  # Fails! ❌
    
    # Now what? Inventory already reduced, payment failed
    # Need: Saga pattern, event sourcing, or compensating transactions

Much harder.

❌ 4. Testing Complexity

Monolith:

$ pytest  # Test entire app

Microservices:

# Need to test:
1. Individual services (unit tests)
2. Service interactions (integration tests)
3. End-to-end flows (E2E tests)

# Requires:
- Mock services
- Test environments for all services
- Contract testing

❌ 5. Debugging Difficulty

Monolith error:

Stack trace shows:
  File "app.py", line 45, in create_order
  File "payment.py", line 23, in charge_payment
  
Clear path: app.py β†’ payment.py

Microservices error:

Order Service β†’ Calls Payment Service β†’ Calls Fraud Detection Service β†’ Fails

Logs scattered across 3 services
Need distributed tracing (Jaeger, Zipkin)

When to Use Monolithic Architecture

βœ… 1. Starting a New Project

Early-stage startup:

  • Small team (< 10 developers)
  • Uncertain requirements
  • Need to ship fast

Start monolith, split later if needed.

Real Example: Airbnb, Twitter, Shopify all started as monoliths.


βœ… 2. Small Applications

Internal tools:

  • Admin dashboards
  • Employee directory
  • Simple CRUD apps

No need for microservices complexity.


βœ… 3. Tight Budget / Small Team

Microservices require:

  • DevOps expertise
  • Infrastructure (Kubernetes, service mesh)
  • Monitoring tools

If you don't have resources, stick with monolith.


When to Use Microservices

βœ… 1. Large Teams (50+ Developers)

Netflix:

  • 1,000+ developers
  • Impossible to coordinate in single codebase
  • Microservices enable team autonomy

βœ… 2. Need Independent Scaling

Uber:

  • Ride matching service: High traffic, needs 100+ instances
  • Driver payout service: Low traffic, needs 2 instances

Microservices save costs (don't scale everything).


βœ… 3. Different Technology Requirements

Real Example: Spotify

  • Recommendation engine: Python (TensorFlow)
  • Real-time streaming: C++ (performance)
  • Web API: Node.js (async I/O)

Microservices allow using best tool for each job.


βœ… 4. Frequent Deployments

Amazon:

  • Deploys every 11.6 seconds (used to be 11.6 seconds, now even faster)
  • Only possible with microservices (deploy one service at a time)

Migration: Monolith to Microservices

The Strangler Fig Pattern

Don't rewrite from scratch! Gradually extract services.

Steps:

1. Identify independent module (e.g., Payment)
2. Extract into separate service
3. Route traffic to new service
4. Keep monolith running
5. Repeat for next module

Example: Uber

2010: Monolithic Python app

Migration:

Year 1: Extract dispatch service β†’ Microservice
Year 2: Extract payment service β†’ Microservice
Year 3: Extract driver tracking β†’ Microservice
...
Year 5: Monolith mostly gone (legacy code only)

By 2020: Over 2,000 microservices.


Don't Split Too Early

Common mistake: "Let's start with microservices!"

Problem:

  • Unknown domain boundaries (what should be a service?)
  • Over-engineering (3 users don't need 50 microservices)
  • Development slows down (complex infrastructure)

Better: Start monolith, split when pain points emerge.

Signals to split:

  • Team > 20 developers
  • Deployment takes > 1 hour
  • Frequent merge conflicts
  • Hard to scale specific features

Hybrid Approach: Modular Monolith

Best of both worlds:

Structure:

Monolithic Application
  β”œβ”€β”€ User Module (clear boundaries)
  β”œβ”€β”€ Product Module (independent)
  β”œβ”€β”€ Order Module (loosely coupled)
  └── Payment Module (can be extracted later)

Characteristics:

  • Single deployment
  • But clear module boundaries
  • Easy to extract into microservice later

Real Example: Shopify

Shopify uses modular monolith:

  • Fast development
  • Clear boundaries
  • Can extract services when needed

Decision Matrix

FactorMonolithMicroservices
Team Size< 20 developers50+ developers
MaturityEarly stageMature product
Scalability NeedUniformNon-uniform
Deployment FrequencyWeeklyDaily
Complexity ToleranceLowHigh
BudgetTightFlexible

System Design Interview Tips

Common Question: "Design Uber"

Answer:

Start with context:

"Initially, Uber was a monolith. As they scaled, they migrated to microservices. I'll design the microservices version."

Microservices breakdown:

1. User Service (riders, drivers)
2. Ride Matching Service (match rider to driver)
3. Trip Service (ongoing trips)
4. Payment Service (process payments)
5. Notification Service (push notifications)
6. Pricing Service (surge pricing)
7. Map Service (routing, ETA)

Why microservices for Uber?

βœ… Independent scaling (ride matching needs 100x more capacity than payment)
βœ… Large team (1,000+ engineers)
βœ… Frequent deployments (ship features independently)
βœ… Fault isolation (map service down β‰  payment down)

What to Mention

βœ… Start by clarifying team size, scale, and requirements βœ… Mention trade-offs (complexity vs autonomy) βœ… For small scale: Suggest monolith βœ… For large scale: Suggest microservices with clear boundaries βœ… Mention communication (REST, gRPC, message queues) βœ… Discuss data consistency (eventual consistency, sagas)


Avoid These Mistakes

❌ Saying "microservices are always better" (context matters!) ❌ Not mentioning disadvantages (shows lack of depth) ❌ Over-engineering (5 users don't need 50 microservices) ❌ Not explaining service boundaries (how did you split?)


Conclusion

Monolithic vs Microservices isn't "better" or "worse"β€”it depends on context:

Monolith:

  • Simple, fast to develop
  • Great for small teams, early-stage products
  • Lower operational overhead

Microservices:

  • Complex, but enables team autonomy
  • Necessary for large teams, high scale
  • Expensive to operate

The truth? Most successful companies started with monoliths and migrated to microservices as they scaled.

Your path:

  1. Start monolith (or modular monolith)
  2. Grow your team and product
  3. Extract services when pain points emerge
  4. Gradually migrate (strangler fig pattern)

Don't prematurely optimize. Build a house first. When it grows into a city, you'll know when to split.


Cover image by AJ Robbie on Unsplash

Support My Work

If this guide helped you learn something new, solve a problem, or ace your interviews, I'd really appreciate your support! Creating comprehensive, free content like this takes significant time and effort. Your support helps me continue sharing knowledge and creating more helpful resources for developers and students.

Buy me a Coffee

Every contribution, big or small, means the world to me and keeps me motivated to create more content!

Related Blogs

Ojaswi Athghara

SDE, 4+ Years

Β© ojaswiat.com 2025-2027