Skip to main content

Command Palette

Search for a command to run...

Monolithic Architecture: The Default That Gets Abandoned Too Early

Updated
11 min read
Monolithic Architecture: The Default That Gets Abandoned Too Early

Series: System Design · Architecture Patterns — Pillar 7 of 8

Systems Design

# Post What it covers
00 Architecture Patterns: How Systems Are Structured Twenty patterns covering monoliths, microservices, events, resilience, deployment, and data processing. How to structure systems that survive growth.
01 Monolithic Architecture: The Default That Gets Abandoned Too Early ← you are here Monoliths are fast to build and easy to operate. Learn when they're the right choice, when they break down, and how to know the difference.
02 Microservices: The Architecture You Earn, Not Choose Microservices enable independent scaling and team autonomy — but at significant cost. Learn what you actually get, what you pay, and when it's worth it.
03 Serverless: Pay for What You Use, Not What You Provision Serverless scales to zero and charges per invocation. Learn where it shines, where it fails, and how to design around cold starts and vendor lock-in.
04 Event-Driven Architecture: Decoupling Through Events Event-driven systems communicate via events rather than direct calls. Learn how producers, consumers, and event brokers work — and the consistency tradeoffs involved.
05 Message Queues: Decoupling Produce from Consume Message queues decouple producers and consumers, enable load levelling, and provide durability. Learn how they work and when to use Kafka vs SQS vs RabbitMQ.
06 Pub/Sub: Broadcasting Events to Multiple Consumers Pub/sub decouples publishers from subscribers through topics. Learn how it differs from message queues and when to use Kafka, SNS, or Google Pub/Sub.
07 CQRS: When Reads and Writes Need Different Models CQRS separates writes from reads so each can be optimised independently. Learn how it works, when it's worth the complexity, and when it isn't.
08 Event Sourcing: The Ledger, Not the Balance Event sourcing stores state as a sequence of events. Learn how it works, what you get (audit log, time travel), and what it costs (complexity, schema evolution).
09 The Saga Pattern: Distributed Transactions Without Locks The Saga pattern manages distributed transactions across services using compensating transactions. Learn choreography vs orchestration and when to use each.
10 The Outbox Pattern: Atomic Writes and Event Publishing The Outbox pattern solves the dual-write problem — publishing an event and writing to a database atomically. Learn how it works using CDC or polling.
11 The Circuit Breaker: Stopping Cascading Failures Circuit breakers prevent cascading failures by fast-failing calls to unhealthy dependencies. Learn the three states, how to configure them, and where to apply them.
12 The Bulkhead Pattern: Containing Failures Through Resource Isolation Bulkheads isolate thread pools and connections per dependency so one failure can't exhaust resources needed by others. Learn how to apply them in practice.
13 The Sidecar Pattern: Cross-Cutting Concerns Without Code Changes The sidecar pattern deploys a helper process alongside each service for logging, metrics, TLS, and service discovery — without modifying the service itself.
14 Service Mesh: A Programmable Network for Microservices A service mesh handles service-to-service traffic, mTLS, circuit breaking, and observability via a fleet of sidecar proxies. Learn how it works and when to use it.
15 Service Discovery: Finding Services in a Dynamic Environment Service discovery lets services find each other in dynamic environments. Learn client-side vs server-side discovery, health checks, and DNS vs registry approaches.
16 The Strangler Fig: Replacing a Legacy System Without Burning It Down The Strangler Fig replaces a legacy system incrementally by routing specific functionality to new implementations while the old system keeps running.
17 Backend for Frontend: One API Per Client Type BFF creates dedicated API backends per client type. Learn why one general API struggles to serve mobile and web well, and how BFF solves it.
18 ETL Pipelines: Moving Data from Operations to Analytics ETL moves data from operational systems into analytical stores. Learn how pipelines work, what ELT is, and how to design reliable data movement at scale.
19 Batch vs Stream Processing: How Fresh Do Your Answers Need to Be? Batch processes accumulate data then processes in bulk; streaming processes each event as it arrives. Learn the tradeoffs and when each is right.
20 MapReduce: Processing Petabytes in Parallel MapReduce processes massive datasets in parallel by splitting work into map and reduce phases. Learn how it works and why Spark has largely replaced it.
21 Architecture Patterns: Wrap-Up A recap of all 20 architecture patterns across decomposition, async communication, data patterns, resilience, and data processing. How they connect.

Monolithic Architecture: The Default That Gets Abandoned Too Early

The problem

Every successful system begins as a monolith. Every architecture astronaut wants to decompose it into microservices immediately.

The instinct is understandable — microservices sound more scalable, more modern, more professional. But the teams that decompose too early spend months building distributed systems infrastructure that they don't need yet, while the teams that keep things simple ship features. By the time the monolith genuinely hurts, the simple team has ten times the functionality.

The question isn't whether a monolith is good or bad. It's whether your system's current constraints justify the cost of decomposition.


The core idea

A monolithic architecture packages the entire application — all modules, all features, all data access — into a single deployable unit. One codebase, one build, one deployment. All components run in the same process and communicate via function calls rather than network requests.


The analogy: a single kitchen doing everything

A small restaurant has one kitchen that handles everything: appetisers, mains, desserts, and drinks. The head chef knows every station, the team communicates face-to-face, dishes are coordinated in real time. It works well — until the restaurant gets large enough that the kitchen can no longer handle the volume, the menu complexity, or the team size.

At that point, you might open a separate pastry kitchen, a separate bar, a catering arm. But you'd be foolish to build separate kitchens before the volume demands it — it just adds coordination overhead and cost that you can't yet justify.


How monolithic architecture works

The structure

Every component in the system communicates by calling functions — not HTTP requests, not gRPC, not message queues. The link creation logic calls the analytics logic directly. The user service calls the billing service directly. No serialisation, no network latency, no retry logic.

What this buys you

Developer velocity. Running the entire system locally takes one command. Debugging means setting a breakpoint anywhere in the codebase — the call stack crosses service boundaries seamlessly because there are no service boundaries. Cross-cutting changes (adding a field to the data model, renaming a concept) touch one codebase in one PR.

Operational simplicity. One build artefact. One deployment pipeline. One set of logs. One place to look when something breaks. Scaling means starting more instances of the single binary.

Performance. In-process function calls have nanosecond latency. The equivalent cross-service call has millisecond latency (network + serialisation + deserialisation). In a monolith, you can call a function a thousand times in a request without caring. In microservices, you think hard about every service boundary crossing.

Transactional simplicity. All operations can be wrapped in a single database transaction. Consistency is free — the database guarantees it. In microservices, each service has its own database; cross-service consistency requires distributed transaction patterns (Saga, Outbox) that are much more complex.

The real problems that emerge at scale

A monolith doesn't have inherent flaws — it has scaling constraints that appear at specific thresholds:

Team size. When ten teams are all merging to one codebase, deployments block each other. "I can't deploy my feature until their merge conflict is resolved." Coordination overhead grows quadratically with team size. At some point, independent deployability becomes a necessity.

Independent scaling. In the URL shortener, the redirect engine processes a million requests per second but the analytics pipeline processes a thousand requests per minute. In a monolith, scaling the redirect engine means scaling the analytics pipeline too — you can't deploy more of one without the other.

Technology lock-in. The entire codebase must use the same language, runtime, and major libraries. If you want to write the redirect engine in Rust for performance, you can't — the whole system is in Ruby.

Failure blast radius. A bug in the billing code that crashes the process takes down the redirect engine with it. In a monolith, every component shares a fate.

Build time. A large monolith may take 10–20 minutes to build and test. Every change requires rebuilding and retesting everything.

Modular monolith: the middle ground

The failure mode of a monolith isn't size — it's coupling. A well-structured modular monolith maintains strict module boundaries, with clearly defined interfaces between components, even though everything deploys together.

Modules communicate through well-defined interfaces (or internal events), not through direct data-layer access. When you eventually extract a module into a separate service, it's a well-understood operation — the interface is already defined.


Tradeoffs

Simplicity now vs flexibility later. A monolith is faster to build and operate today. The cost is paid later: when you need to scale one component independently or deploy one team's changes without coordinating others. That cost is real — but it often arrives later than engineers anticipate.

Coupling risk. The biggest risk of a monolith isn't technical — it's architectural entropy. Without discipline, a monolith drifts into a "big ball of mud" where everything depends on everything else, no module has clear ownership, and changes become risky because you can't predict what they'll break.

Horizontal scaling limits. A monolith can scale horizontally (run N copies behind a load balancer). What it can't do is scale one module more than others. If the bottleneck is one component in a large deployment, you're paying for the whole binary when you only need more of one part.


When to use it / when not to

Start with a monolith when:

  • Team is small (under ~20–30 engineers)

  • System requirements aren't yet stable (you'll refactor frequently)

  • You haven't yet discovered where the natural service boundaries are

  • Speed of iteration matters more than independent scalability

Consider decomposing when:

  • Specific components need to scale independently and have proven bottlenecks

  • Multiple teams are blocked on each other's deployments regularly

  • Components need different technology stacks for legitimate reasons

  • A module has significantly different operational characteristics (availability needs, deployment frequency)

Don't decompose because:

  • Microservices feel more "enterprise"

  • You anticipate needing scale you don't have yet

  • The team is small enough that coordination isn't actually a problem


The one thing to remember

A monolith is not a failed microservices architecture — it's the correct starting architecture for most systems. The problems that microservices solve (independent scalability, team autonomy, technology diversity) are real, but they only become problems at a specific scale. Before that scale, microservices are overhead. Maintain discipline within the monolith — clear module boundaries, no cross-module data layer access — so that when decomposition is genuinely warranted, the boundaries are already there.


← Previous: Pillar 7 Overview — introducing the architecture patterns pillar

→ Next: Microservices — when the monolith genuinely constrains you, here's what decomposing actually involves and what you're buying.

Systems Design

Part 1 of 50

Understanding these system design concepts is essential for architects, developers, and engineers to create scalable, reliable, and maintainable software systems that meet the needs of businesses.

More from this blog

Cloud Tuned

751 posts

Your starting point for anything cloud: AWS, Azure, GCP, Serverless, Architecture, Hybrid Cloud, Systems Design and other Information Technology topics.