Side by side
| Dimension | Monolith | Microservices |
|---|---|---|
| Deployment | Modules ship as one deployable application | Services can ship independently |
| Scaling | Replicate the application as a unit | Scale individual services |
| Internal communication | Mostly in-process calls | Network APIs or messages across services |
| Transactions | Local database transactions are easier to use | Cross-service changes need explicit coordination |
| Data ownership | Modules can share one database | Services should own their data and access contracts |
| Failure isolation | Modules share process resources and failures | Isolation is possible, but failures can cascade |
| Operations | Fewer deployment and debugging boundaries | More routing, telemetry, and compatibility work |
| Team coordination | Shared releases require coordination | Clear ownership can enable independent releases |
The differences that matter
Deployment boundaries are not code-quality boundaries. A modular monolith can enforce clear interfaces between orders, inventory, and billing while deploying them together. Conversely, services that must always release together form a distributed monolith: they add network failure modes without gaining release independence.
Scaling changes at the deployment boundary. A stateless monolith can run many replicas behind a load balancer, so high traffic alone does not require microservices. Splitting becomes useful when one workload needs a different resource profile, such as CPU-heavy image processing versus latency-sensitive metadata reads. Neither approach automatically removes a database bottleneck.
Data boundaries change correctness work. Modules using one transactional database can update related records atomically. If inventory and orders own separate databases, creating an order and reserving stock requires an explicit workflow, including what happens after partial success. An outbox can reliably publish committed changes; it does not make the entire workflow one atomic transaction.
Network boundaries change latency and failure handling. A local method call becomes an operation that can time out after the remote side has committed. Microservices therefore need bounded retries, idempotency, deadlines, and useful tracing. Separate processes can isolate crashes, but synchronous dependency chains and shared infrastructure can still spread failures.
Choose Monolith when
- A small team owns the product, and independent service releases would add more operational work than they remove.
- Domain boundaries are still changing, so moving code between modules is preferable to migrating APIs and data ownership.
- Several core operations need atomic updates in one database, and splitting them would introduce unnecessary coordination.
- Application replicas meet capacity needs, with no measured workload that needs its own scaling or deployment boundary.
Choose Microservices when
- A clearly bounded workload needs independent scaling, such as media transcoding that should not consume request-serving capacity.
- Multiple teams own stable business capabilities and need to release without coordinating every application deployment.
- A component needs a separate failure boundary, and the design also limits dependency propagation through queues, deadlines, or resource isolation.
- A subsystem has distinct runtime or security requirements, and the team can support independent deployments, monitoring, and versioned contracts.
A worked example
Separate image processing, not every product module
Suppose a photo application serves 2,000 metadata requests per second and accepts 40 uploads per second. In this hypothetical workload, each upload creates six image-processing jobs, producing 240 jobs per second before retries. Keep accounts, albums, and permissions in a modular monolith if one team owns them and local transactions simplify updates. Run image processing as independently scaled workers behind a durable queue so CPU-heavy jobs do not compete with metadata requests. Workers must tolerate duplicate jobs and publish results safely. This hybrid creates one useful scaling boundary without requiring separate services for every module. Validate capacity with measurements rather than inferring instance counts from request rates alone.
In the interview
- Start with requirements rather than drawing a service for every noun. State which workloads need independent scaling, which operations require atomicity, and who owns releases.
- Keep the logical component diagram separate from the deployment diagram. Explain that catalog, orders, and inventory can initially be modules in one application.
- Walk through one write across the proposed boundary. Expect questions about partial success, duplicate messages, timeouts, compensation, and which component owns the authoritative state.
- Explain both the initial choice and a migration trigger. For example, extract image processing when its resource demands interfere with API latency, using a stable interface and a staged migration.
Common mistakes
- Claiming a monolith runs on only one server. Stateless application replicas can scale horizontally; shared state and database capacity still need separate analysis.
- Treating microservices as automatic fault isolation. Shared databases, unbounded retries, and synchronous call chains can preserve or increase the blast radius.
- Splitting services while allowing unrestricted writes to shared tables. This retains data coupling and undermines independent releases.
- Adding a saga to every workflow or assuming every monolith operation is atomic. Use local transactions where possible, and recognize that external payments already cross a transactional boundary.
Where it comes up
Interview questions where choosing between Monolith and Microservices comes up.
- Design a notification systemChannel-specific workers illustrate useful scaling and failure boundaries without requiring every notification function to become a service.
- Design a video streaming service like YouTubeTranscoding has a distinct resource profile that justifies separating media processing from metadata request handling.
- Design a ticket booking system like TicketmasterSeparating inventory, booking, and payments forces an explicit discussion of atomic holds, partial success, and cross-boundary recovery.
- Design a food delivery app like DoorDashOrders, restaurant preparation, and courier assignments expose the coordination costs of independently owned services.
Questions people ask
Can a monolith scale as well as microservices?
A monolith can scale horizontally by running multiple application replicas, especially when request handling is stateless. Microservices offer finer-grained scaling, which helps when workloads have different resource needs. Actual capacity depends on bottlenecks such as database contention, external dependencies, and shared state.
Should a startup use a monolith or microservices?
A modular monolith is often a practical starting point when one small team owns the application and domain boundaries are changing. Microservices make sense earlier when a specific capability already needs independent scaling, ownership, or isolation. Team size alone is not a sufficient decision rule.
How do you migrate a monolith to microservices?
Choose a bounded capability with a concrete reason for independent deployment or scaling, then establish an interface around it. Use a strangler-fig migration to route functionality gradually while moving data ownership deliberately. Plan compatibility, rollback, and reconciliation so the transition does not depend on unsafe dual writes.