When to Use Microservices vs Start with a Monolith
Fast recall
Quick revision
Core answer
Interview-ready guide
Interview answer
I start with the simplest architecture that supports the current team and reliability needs. A modular monolith is often a strong option for one team and an evolving domain because calls and transactions stay local while module boundaries can still be enforced. It is not a disposable prototype; it can be a long-term architecture.
I consider a microservice boundary when a capability has stable ownership and independent deployment, scaling, compliance, or failure-isolation needs. For example, a media-transcoding workload may need a different release and compute profile from an account API. I would extract it behind a clear contract and data-ownership boundary rather than splitting every entity into a service.
Microservices add network failure, eventual consistency, contract evolution, security between services, observability, and many deployment units. Therefore 'start monolith, split later' is a useful default question, not a universal law. The decision should name the measured benefit that pays for those costs.
When to Use Microservices vs Start with a Monolith
Direct Answer
A modular monolith is often a good starting point when one team needs fast iteration and boundaries are still changing. Choose microservices when independently deployable boundaries solve a measured need such as team autonomy, release isolation, regulatory separation, or distinct reliability/scaling demands. This is a context decision, not a rule that every system must begin monolithic or later split.
Code Example
// MonolithFirst: build a well-structured MODULAR monolith. Each module is a
// clean package with a clear interface you can later extract without rewiring.
package com.shop.inventory; // module boundary
public interface InventoryService { // the contract callers depend on
boolean reserve(String productId, int qty);
}
// In the monolith: a simple in-process bean
@Service
class LocalInventoryService implements InventoryService { /* local DB */ }
// Extract LATER, only when real pain appears (scaling or team coordination):
// swap the implementation for a REST client — callers never change.
@Service
class RemoteInventoryService implements InventoryService {
private final WebClient client; // now calls inventory-service over HTTP
public boolean reserve(String id, int qty) { /* network call */ return true; }
}
Common Mistakes & Pitfalls
- Premature optimization for scale: Freshers argue for microservices because the app might have a million users someday. Monoliths handle millions of users fine—Instagram ran on a Django monolith for years. Scale is rarely the reason to split; team coordination and deployment independence are.
- Splitting before domain clarity: Extracting services before understanding domain boundaries creates a high risk of misplaced boundaries. You'll have services that call each other constantly, negating independence. Wait until patterns emerge—user management is clearly separate from order processing.
- Ignoring the operational tax: Freshers underestimate the infrastructure needed: Kubernetes or ECS, service mesh, distributed tracing, log aggregation, secrets management, and CI/CD for every service. A small team should verify that it can sustain that platform work while building features. The operational cost of microservices is higher than the code complexity they're supposed to solve.
Check your understanding
Answer these without looking back at the article.
- What is a distributed monolith and why is it problematic?
- What is domain-driven design and how does it help define microservice boundaries?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.