Background
Sections
IntroductionRequirements & Problem AnalysisConstraints & AssumptionsEstimation TechniquesFunctional vs Non-Functional RequirementsMoSCoW PrioritizationSystem Design FundamentalsArchitecture DiagramClass DiagramComponent DiagramData Flow Diagram (DFD)ER Diagram (Entity-Relationship Diagram)High Level Design (HLD)Low Level Design (LLD)Sequence DiagramState DiagramUse Case DiagramData StorageDocument StoresFile StorageGraph DatabasesIn-Memory DatabasesKey-Value StoresNewSQLNoSQL DatabasesObject StorageSQL Databases (RDBMS)Time-Series DatabasesWide-Column StoresDatabase ConceptsACID PropertiesCAP TheoremConsistency ModelsIndexingNormalization & DenormalizationReplicationSharding & PartitioningTransactions & Isolation LevelsScalabilityAuto-Scaling & ElasticityConsensus & Leader ElectionLoad BalancingReplication & Read ReplicasSharding & PartitioningVertical vs Horizontal ScalingAvailability & ReliabilityBackup & Data DurabilityCircuit BreakerData ConsistencyDisaster RecoveryFault Tolerance & FailoverGraceful DegradationHigh AvailabilityNetworkingCDNDNSFirewalls & VPNHTTP & HTTPSLoad Balancer & Reverse ProxyTCP/IP & UDPWebSocketsCachingCache InvalidationCache Read/Write PatternsCaching LayersEviction PoliciesRedis vs MemcachedMessaging & CommunicationDead-Letter QueueIdempotencyKafka vs RabbitMQ vs SQSMessage QueuesPub/SubCompute & ServicesAPI GatewayContainers & OrchestrationMonolith vs MicroservicesServerlessService DiscoveryService MeshWeb Server & App ServerAPI DesignAPI Versioning & IdempotencyAuthentication & AuthorizationGraphQLgRPCPaginationRate Limiting & ThrottlingRESTSecurityAuthentication & AuthorizationData PrivacyEncryptionInput Validation & InjectionOAuth2 & JWTSecrets ManagementXSS & CSRFStorage & File SystemsBackup & RetentionBlock vs File vs Object StorageData Lakes & WarehousesDistributed File SystemsEphemeral StorageObservability & MonitoringDistributed TracingHealth ChecksLoggingMetricsSLI, SLO, SLADesign PatternsBulkhead PatternCircuit Breaker PatternCreational PatternsRate Limiter PatternRetry PatternStructural & Behavioral Patterns

Monolith vs Microservices

7 min read

In a Nutshell

A monolith is an application built and deployed as a single unit — all the code, all the features, one codebase, one deployment. Microservices split that application into many small, independently-deployable services, each owning one business capability and communicating over the network. The choice between them is one of the most consequential — and most over-argued — architectural decisions. Microservices offer independent scaling, deployment, and team autonomy, but at the cost of enormous operational and distributed-systems complexity. The modern consensus is nuanced: most systems should start as a well-structured monolith and extract microservices only when specific, real pressures justify the complexity.

2D minimalistic diagram split in two: left labeled "Monolith" shows one large box containing all modules (users, orders, payments, inventory) deployed as a single unit; right labeled "Microservices" shows the same capabilities as separate small service boxes, each with its own database, connected by network arrows, deployed independently

How It Actually Works

The Core Trade-off

Dimension Monolith Microservices
Deployment One unit Many independent units
Scaling Whole app scales together Scale each service independently
Codebase Single Many (per service)
Team autonomy Coordinated Teams own services independently
Communication In-process function calls (fast, reliable) Network calls (slow, can fail)
Data Shared database Database per service
Consistency Easy (ACID transactions) Hard (distributed, eventual)
Operational complexity Low High (many services, networking, observability)
Failure isolation A bug can crash everything Failures can be contained
Local development Simple (run one thing) Complex (many services + infra)

The Monolith's Underrated Strengths

Monoliths get unfairly maligned. Their advantages are real and easy to lose:

  • Simplicity: one codebase, one deploy, one thing to run locally and debug.
  • Performance: in-process calls are nanoseconds; network calls are milliseconds and can fail.
  • Transactions: a single database means easy ACID consistency across the whole app.
  • Refactoring: changing a boundary is a code change, not a cross-service API negotiation.

A well-structured monolith — internally modular with clear boundaries (a "modular monolith") — captures most of the organizational benefits without the distributed complexity.

When Microservices Earn Their Complexity

Microservices are a solution to specific problems, most of them organizational and scaling-related:

Pressure Why Microservices Help
Independent scaling One component (e.g., video encoding) needs far more resources than the rest
Team autonomy Many teams stepping on each other in one codebase; independent deploys unblock them
Independent deployment Need to ship one part without redeploying/retesting everything
Technology diversity Different services genuinely need different languages/datastores
Fault isolation A failure in one capability must not take down others

If you don't have these pressures, microservices mostly add cost.

The Costs You Take On

Going distributed means inheriting the hard problems:
  • Network calls fail, are slow, need retries/timeouts/circuit breakers
  • No cross-service ACID → sagas, eventual consistency
  • Distributed tracing/observability becomes essential (not optional)
  • Deployment/orchestration complexity (containers, service discovery, mesh)
  • Data duplication and sync across service databases
  • Testing spans many services + their interactions
  • "Distributed monolith" risk: services so coupled you get the worst of both

The distributed monolith is the worst outcome: services that must be deployed together and call each other synchronously in tight chains — all the operational cost of microservices with none of the independence.

The Pragmatic Path: Monolith First

Start:   well-structured modular monolith (clear internal boundaries)
   │
Grow:    hit a REAL pressure (a component needs independent scaling,
   │     a team needs autonomy, a part needs isolated failure)
   ▼
Extract: peel that specific capability into its own service
         (the "strangler fig" pattern — extract incrementally)

Starting monolith-first lets boundaries emerge from real understanding, rather than guessing service boundaries upfront (which is when you get them wrong and pay dearly).

2D minimalistic diagram showing the "monolith-first, extract later" evolution: a modular monolith box on the left with dashed internal module boundaries; an arrow labeled "extract under real pressure" pointing right; and the result where one module (e.g., "payments") has been peeled off into its own independent service while the rest remains a monolith, illustrating incremental extraction

Seeing It in Action

Scenario: A startup's architecture journey.

Year 1 — MVP, 3 engineers:
  → MONOLITH. Single Rails/Django/Spring app, one Postgres.
    Ship fast, iterate, find product-market fit. Microservices here
    would be premature — pure overhead slowing a tiny team.
    Do keep it modular internally (clear module boundaries).

Year 2 — Growth, 15 engineers, some scaling pain:
  → STILL mostly monolith, but pressures emerging:
    - Image/video processing is CPU-heavy and spiky → EXTRACT it into
      a separate service that scales independently (real scaling pressure).
    - Payments needs strict isolation + compliance → EXTRACT it
      (real fault-isolation pressure).
    Everything else stays in the monolith. Extract only what hurts.

Year 3 — Scale, 60 engineers across many teams:
  → SELECTIVE MICROSERVICES. Teams own services (checkout, catalog,
    search, notifications) for autonomy and independent deploys.
    Backed by real infra: API gateway, service discovery, tracing,
    orchestration. The complexity is now JUSTIFIED by team size and scale.
    The core may still be a monolith — that's fine.

The guiding principle: microservices are an answer to organizational and scaling pressure, not a default or a resume line. Each extraction should be driven by a concrete pain (this needs to scale alone, this team needs autonomy, this must fail in isolation), not by architectural fashion. Many wildly successful companies run large monoliths; many others were nearly killed by prematurely fragmenting into microservices they couldn't operate. Start simple, keep clean boundaries, and extract deliberately.

Interview Questions

  1. Q: What are the main trade-offs between a monolith and microservices? Hint: Monolith: simple to build/deploy/debug, fast in-process calls, easy ACID transactions, but scales as one unit and can become a bottleneck for large teams. Microservices: independent scaling/deployment/team autonomy and fault isolation, but huge operational and distributed-systems complexity (network failures, eventual consistency, observability, orchestration). The core trade is simplicity vs independence.

  2. Q: When should you choose microservices over a monolith? Hint: When you have specific pressures: a component needs independent scaling, many teams need autonomy and independent deploys, parts need fault isolation, or services genuinely need different tech stacks. Absent these (small team, early product), a well-structured monolith is better. Microservices solve organizational/scaling problems at the cost of complexity — only pay it when the problems are real.

  3. Q: What is a "distributed monolith" and why is it the worst outcome? Hint: Services that are so tightly coupled they must be deployed together and call each other synchronously in tight chains — you get all the operational cost and failure modes of microservices with none of the independence benefits. It usually results from splitting a monolith along the wrong boundaries. Avoid by extracting along genuine business capabilities with loose, async coupling.

  4. Q: Why is "monolith first" often recommended? Hint: Service boundaries are hard to get right upfront; guessing wrong is very costly to fix once distributed. Starting with a modular monolith lets boundaries emerge from real understanding of the domain, keeps early development fast and simple, and lets you extract services incrementally (strangler fig) under real pressure. You avoid premature complexity while preserving the option to split later.

  5. Q: What new problems do you inherit by going distributed? Hint: Network calls that are slow and fail (needing timeouts, retries, circuit breakers), loss of cross-service ACID (sagas, eventual consistency), mandatory distributed tracing/observability, deployment and orchestration complexity (containers, service discovery, mesh), data duplication/sync across per-service databases, and harder end-to-end testing. Plus the risk of accidentally building a distributed monolith.

References

Dive Deeper