Monolith vs Microservices

Monolith vs microservices: deployment, team ownership, data boundaries and operational cost, plus the modular monolith in between.

Beginner 6 min read · Updated

A monolith is one unified application: user interface, business logic and data access live in one codebase, run as one process and are deployed together, usually with one database. Microservices split the product into small, loosely coupled services, each responsible for one capability and owning its own database, deployed independently and talking to each other over APIs. The choice is mostly about teams and change, not raw scalability: microservices buy independent deployment and scaling at the price of distributed-systems complexity.

Monolithic architecture

Take an e-commerce store. As a monolith, managing products, carts, orders and payments are all modules inside one application, sharing one database and calling each other as ordinary function calls.

One application, one deployClientInventory moduleOrder modulePayment moduleDatabase
A monolithic e-commerce store: every module lives in one application and shares one database.
ProsCons
Simplicity: easy to build, test and deploy while the app is smallReliability: a failure in one part (a memory leak in catalog code) can bring down everything
Performance: modules call each other in-process, which is fastScalability: you can't scale one busy part on its own; you scale the whole app
Easy debugging: all the code is in one place, so issues are easy to traceComplexity at scale: the codebase gets hard to maintain; a new engineer on the payments team has to learn the inventory and order code too
Low initial cost: little infrastructure or process overheadDeployment bottleneck: a change to one module means redeploying the whole application

Microservices architecture

In the microservices version, the product catalog, cart, orders and payments are each their own service with their own database, and they communicate over APIs.

ClientAPI gatewayProduct serviceUser serviceOrder servicePayment serviceProduct DBUser DBOrder DBPayment DB
The same store as microservices: an API gateway routes to independent services, each owning its database.
ProsCons
Scalability: scale each service on its own loadComplexity: many services to deploy, monitor and operate
Resilience: one service failing doesn't have to take down the restInter-service communication: network calls fail and add latency, so you need timeouts, retries and circuit breakers
Flexibility: each service can use the language and tools that suit itData consistency: keeping data consistent across services is hard, and transactions across services are difficult
Faster development: teams work on different services in parallelHigher initial cost: more infrastructure for orchestration, discovery and monitoring
Continuous deployment: ship one service without touching the others

Why teams move to microservices

A startup builds an e-commerce app as one codebase: catalog, checkout, payments, accounts. It handles 100 orders a day and the monolith works well.

Two years later it handles 10,000 orders a day, and the cracks show. Checkout needs more servers, but you can't scale just checkout; you must scale the whole monolith. The payments team wants to ship fraud detection but can't deploy without coordinating with every other team. A bug in the catalog crashes the application and takes checkout and payments down with it.

Splitting into product, cart, user, order and payment services fixes exactly these problems: each service deploys independently, scales independently and fails independently.

How microservices work

  • Separate processes, talking over APIs. Each service runs on its own and communicates through APIs, usually REST over HTTP or gRPC. The payment service calls the order service to verify an order; the order service calls the inventory service to check stock. See synchronous vs asynchronous communication.
  • Each service owns its database. The user service manages the user database, the order service the order database, and no service reads another's tables. If the user service changes its schema, nothing else breaks. See database per service.
  • Service discovery. Services register with a service registry (Consul, Eureka). When the payment service starts it announces its address; callers ask the registry where to find it. See service discovery.
  • Independent scaling. On Black Friday, checkout traffic spikes: run 10 more order-service instances while the product service stays at 2. Different services have different load patterns.
  • Fault isolation. If the recommendation service crashes, checkout still works. Circuit breakers detect a failing service and stop sending it requests, preventing cascading failures.
verify ordercheck stockwhere is Order?Payment serviceOrder serviceInventory servicePayment DBOrder DBInventory DBService registryConsul / Eureka
Services call each other through APIs, never through each other's databases, and find each other through a registry.

Common implementations

StackStrengthsGood for
Java + Spring BootBuilt-in REST, service discovery and distributed tracing; mature tooling and strong typingEnterprise systems
Node.js + ExpressFast development, huge npm ecosystemLightweight services, TypeScript or JavaScript teams
Go (Gin, Echo)High performance and built-in concurrencyHigh-throughput, low-latency services such as real-time bidding or stream processing

Comparison

MonolithMicroservices
DeploymentOne unit; every change redeploys everythingEach service deploys independently
Team scalingCoordination grows as teams grow; merge and release contentionTeams own services end to end and move independently
Calls between partsIn-process function calls: fast and reliableNetwork calls: latency, partial failure, retries, versioning
DataOne database, joins and transactions everywhereDatabase per service; cross-service queries and consistency are hard
ScalingScale the whole app (usually fine)Scale hot services independently
Failure isolationA memory leak can take down everythingFailures can be contained, if resilience patterns are in place
TechnologyOne stackEach service can choose (but too much diversity hurts)
Operational costLow: one thing to deploy, monitor and debugHigh: service discovery, tracing, CI/CD per service, on-call per service

The modular monolith

Between the two sits the modular monolith: one deployable, but internally divided into modules with clear boundaries, their own tables and explicit interfaces, and no reaching into each other's data. It keeps the simplicity of one deployment while preparing clean seams to extract services later, if and when a team or scaling need actually requires it.

Microservices in interviews vs in real life

In interviews, most large-scale design questions expect you to lean towards microservices. The real question is how you decompose the requirements into services: which capabilities become services, which data each one owns, and how they talk. The problem decomposition framework helps here.

In real life, microservices are not always the right call. For small applications a monolith is often perfectly sufficient:

  • Premature optimization: building many services before you understand the business domain wastes effort, and you will draw the boundaries in the wrong places.
  • Iterative learning: a monolith lets you learn and reshape the domain model cheaply before paying the overhead of distributed services.

Martin Fowler observed that almost every system he had seen built as microservices from scratch ran into serious trouble, and advised against starting a new project with microservices even when you're sure it will grow large. The best time to split is when you have outgrown the monolith and understand the system and its requirements well.

When microservices make sense

  • Many teams (dozens of engineers or more) are slowing each other down in one codebase and release train.
  • Different scaling or reliability needs: video transcoding needs GPUs and scales differently from the user profile API.
  • Different change rates: a fast-moving recommendations team shouldn't wait on a slow-moving billing release.
  • Strong domain boundaries are understood, so services won't need to change together for every feature.

Doing microservices well

  • Split by business capability (orders, payments, catalog), not by technical layer (a "database service").
  • Own your data: no other service reads your tables; share through APIs or events.
  • Prefer asynchronous events for cross-service workflows, and sagas for multi-step transactions.
  • Invest in the platform first: service discovery, observability with distributed tracing, CI/CD, circuit breakers and timeouts.
  • Avoid the distributed monolith: services that must be deployed together, or that chain synchronous calls through five services for every request, get the costs of both styles and the benefits of neither.

Start with a well-structured monolith and extract services where a real pressure (team autonomy, scaling, isolation) justifies the cost. Many successful companies run large modular monoliths (Shopify, Basecamp, Stack Overflow).

Where it’s used

Amazon and Netflix

Moved to hundreds or thousands of services owned by small teams, with heavy investment in tooling for deployment, discovery and resilience.

Shopify

Runs one of the largest Ruby on Rails monoliths, organized as a modular monolith with enforced component boundaries.

Segment

Publicly described moving from microservices back to a monolith for a part of its system when the operational overhead outweighed the benefits.

Uber

Grew to thousands of microservices, then introduced domain-oriented groupings to tame the complexity.

In an interview

  • Lean towards services for large-scale designs, but justify each boundary by ownership, scaling or isolation needs.
  • Give each service its own data and explain how data flows between them (APIs, events).
  • Address cross-service consistency with sagas and the outbox pattern.
  • Mention the platform you need: discovery, gateway, tracing, resilience patterns; and say a monolith is fine for a small product.