Loading learning content...
We've explored what monoliths are, their substantial benefits, and the challenges they face at scale. Now comes the crucial question: When is a monolith the right architectural choice?
The industry narrative often frames this question backward—assuming microservices are the default and asking when monoliths are acceptable. This framing is wrong.
Monoliths are the default. Distributed systems introduce complexity that must be justified. The burden of proof lies with the alternative, not with simplicity.
This page provides a rigorous framework for making architectural decisions. We'll examine the conditions that favor monolithic architecture, the warning signs of premature decomposition, and how to build monoliths that can evolve when evolution becomes necessary.
By the end of this page, you will have a practical decision framework for evaluating project architecture. You'll understand why most applications should start as monoliths, when to evolve, and how to position for future flexibility. You'll be able to make—and defend—principled architectural decisions.
For nearly every new project, the monolith should be the starting architecture. This isn't conservative thinking—it's pragmatic engineering based on first principles.
Why Monolith First?
"Almost all the successful microservice stories have started with a monolith that got too big and was broken up." — Martin Fowler. This isn't coincidence. It's because you need to discover domain boundaries through building, not architect them a priori.
The "Premature" Axiom
The famous computer science dictum—"Premature optimization is the root of all evil"—applies to architecture too. Microservices are an optimization for specific problems:
If you don't have these problems yet, you're optimizing for future problems while paying complexity costs now. That's premature architectural optimization.
While monoliths are the default, some conditions make them especially appropriate. Recognizing these conditions strengthens your architectural reasoning.
Team Size and Structure
| Team Size | Recommended Architecture | Rationale |
|---|---|---|
| 1-5 developers | Monolith strongly preferred | Full context in every mind; coordination overhead negligible |
| 5-15 developers | Monolith preferred | Manageable coordination; modules can have loose ownership |
| 15-30 developers | Modular monolith or early decomposition | Coordination starting to cost; consider module boundaries |
| 30-75 developers | Evaluate decomposition | Conway's Law pressures emerging; some service extraction may help |
| 75+ developers | Likely distributed | Coordination overhead likely exceeds decomposition complexity |
Domain Characteristics
Some domain characteristics strongly favor monolithic architecture:
Organizational Considerations
Shopify—processing billions of dollars in transactions, serving millions of merchants—runs on a modular monolith built with Ruby on Rails. They've proven that monoliths can achieve remarkable scale when well-architected. Their approach: "modularize within the monolith" rather than distribute prematurely.
Architectural decisions shouldn't be based on gut feeling or industry trends. Here's a structured approach to evaluate your architecture choice.
The Decision Matrix
Rate each factor from 1 (strongly favors monolith) to 5 (strongly favors microservices):
| Factor | 1 (Monolith) | 3 (Neutral) | 5 (Microservices) |
|---|---|---|---|
| Team size | < 10 engineers | 10-50 engineers | 50 engineers |
| Domain maturity | Exploring, unknown | Partially understood | Well-defined boundaries |
| Deployment frequency need | Weekly acceptable | Daily desired | Multiple times per day, independent |
| Scaling requirements | Uniform load | Some variation | Wildly different per feature |
| Transaction requirements | Strong ACID needed | Some eventual consistency OK | Mostly independent data |
| Tech stack needs | Single stack fine | Some polyglot useful | Very different tools per problem |
| Operational maturity | Limited experience | Moderate experience | Strong platform engineering |
| Failure isolation need | Full outage acceptable | Partial degradation desired | Must isolate failures strictly |
Scoring Interpretation:
Critical Insight: This is a starting point, not a formula. A single 5 on "deployment frequency" doesn't override seven 1s. Context matters enormously.
When scoring, use your current state, not your aspirations. If you have 8 engineers but plan to have 100 in two years, score based on 8. You can re-evaluate when you reach 30, 50, 75. Don't pay distributed systems complexity tax today for problems you might have tomorrow.
One of the most common architectural mistakes in modern software development is premature decomposition—adopting microservices before they're justified.
How Premature Decomposition Happens
The Consequences of Premature Decomposition
When you decompose prematurely, you experience all the costs of distributed systems with few of the benefits:
The worst outcome of premature decomposition is the distributed monolith: a system that has all the deployment complexity of microservices but all the coupling of a monolith. Services must deploy together, share databases, and make synchronous calls everywhere. You've kept the monolith's constraints while adding network boundaries. This is strictly worse than either architecture.
Signs You've Decomposed Prematurely
The industry narrative sometimes suggests monoliths are outdated or limited. Reality tells a different story. Here are companies successfully running monolithic or monolith-primary architectures at scale:
Case Studies
| Company | Scale | Architecture | Key Insight |
|---|---|---|---|
| Shopify | Billions in GMV, millions of merchants | Modular Ruby on Rails monolith | Invests in modularization rather than decomposition |
| Stack Overflow | Top 50 website globally | ASP.NET monolith | Aggressive optimization of single codebase |
| Basecamp | Millions of users | Rails monolith | Simplicity enables a small team to ship fast |
| Signal | 100M+ users sending billions of messages | Monolithic backend | Focus on security and reliability over distribution |
| Hey.com | Major email product | Rails monolith | From Basecamp team; monolith enables rapid feature development |
What These Companies Have in Common
They focus on product, not architecture: Architecture is a means to an end. They optimize for shipping value to customers.
They invest in modularization: Instead of decomposing, they create well-defined internal module boundaries.
They have pragmatic leadership: Decision-makers understand the trade-offs and resist trend-following.
They optimize aggressively: Rather than distributing load, they make each node extremely efficient.
They measure actual problems: They don't solve imaginary scaling problems; they solve real bottlenecks.
Stack Overflow serves 1.7 billion page views per month with just 11 web servers and a team of ~50 engineers. They've achieved this through aggressive caching, database optimization, and a culture of performance awareness—not through distributed systems. When asked about microservices, they've said: "We don't do it because we don't need to."
The Lesson
These companies prove that monoliths can scale to impressive levels. The limiting factor isn't the architecture—it's the engineering practices around it. A well-optimized monolith with good architecture will outperform a poorly-designed distributed system every time.
Choosing a monolith doesn't mean ignoring the future. You can—and should—build monoliths with architectural practices that make future evolution possible.
The Modular Monolith Pattern
A modular monolith has well-defined internal boundaries that mirror how you might decompose into services. Each module:
This gives you monolithic deployment simplicity with service-like modularity.
123456789101112131415161718192021222324252627282930
modular-monolith/├── modules/│ ├── orders/│ │ ├── public/ # Public API of this module│ │ │ ├── OrderService.ts # External contract│ │ │ ├── types.ts # Public types│ │ │ └── events.ts # Events this module publishes│ │ ├── internal/ # Implementation details (private)│ │ │ ├── repositories/│ │ │ ├── domain/│ │ │ └── handlers/│ │ └── index.ts # Only exports public API│ ││ ├── inventory/│ │ ├── public/│ │ └── internal/│ ││ ├── payments/│ │ ├── public/│ │ └── internal/│ ││ └── users/│ ├── public/│ └── internal/│├── shared/ # Truly shared utilities only│ ├── kernel/ # Domain primitives (Money, Quantity)│ └── infrastructure/ # Cross-cutting (logging, metrics)│└── app.ts # Wires modules togetherPractices That Enable Evolution
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
// Evolution-ready module communication // orders/public/types.ts - Only export what other modules needexport interface OrderCreatedEvent { orderId: string; userId: string; items: { productId: string; quantity: number }[]; totalAmount: number; createdAt: Date;} // orders/public/OrderService.ts - Public interfaceexport interface IOrderService { createOrder(params: CreateOrderParams): Promise<Order>; getOrder(orderId: string): Promise<Order | null>; getOrdersByUser(userId: string): Promise<Order[]>;} // orders/internal/OrderServiceImpl.ts - Implementationexport class OrderServiceImpl implements IOrderService { constructor( private readonly inventoryService: IInventoryService, // Interface! private readonly paymentService: IPaymentService, // Interface! private readonly eventBus: IEventBus, // Abstraction! private readonly orderRepository: IOrderRepository, ) {} async createOrder(params: CreateOrderParams): Promise<Order> { // Implementation uses injected dependencies const inventory = await this.inventoryService.reserve(params.items); const payment = await this.paymentService.charge(params.payment); const order = await this.orderRepository.save({...}); // Publish event - could be in-memory or message broker await this.eventBus.publish<OrderCreatedEvent>('order.created', { orderId: order.id, userId: params.userId, items: params.items, totalAmount: order.total, createdAt: new Date(), }); return order; }} // When extracting to microservice:// 1. IInventoryService implementation changes from direct call to HTTP client// 2. IEventBus implementation changes from in-memory to Kafka// 3. The OrderServiceImpl code doesn't change at all!With a well-modularized monolith, extraction becomes incremental via the Strangler Fig Pattern: replace one module at a time with a service, routing calls through an anti-corruption layer. We'll cover this pattern in detail in a later module.
While this page argues for monoliths, we must acknowledge that decomposition is sometimes the right answer. Here's how to recognize when:
Strong Indicators for Decomposition
Weak Indicators (Often Misused)
These are sometimes cited as reasons for microservices, but are usually not sufficient alone:
Many teams say, "We'll start with microservices so we don't have to migrate later." This assumes migration is harder than starting distributed—it's not. Starting with a modular monolith gives you speed now and clear extraction paths later. You're optimizing for the wrong thing.
Sometimes you need to advocate for simplicity against industry trends. Here's how to make the case effectively:
The Business Case
The Technical Case
Handling Objections
| Objection | Response |
|---|---|
| "But we need to scale!" | "Horizontal monolith scaling works to impressive levels. Let's solve scaling problems when we have them." |
| "Netflix uses microservices" | "Netflix has 1000+ services and millions of concurrent users. We have neither. We should solve our problems, not theirs." |
| "We'll be stuck" | "A modular monolith can be incrementally decomposed. We're not committing forever." |
| "Engineers want microservices" | "What specific problems do they want to solve? Let's address those directly." |
| "It's the industry standard" | "The most common architecture, by count, is still monolithic. We're in good company." |
We've established a comprehensive framework for when monolithic architecture is the right choice. Let's consolidate:
The Bottom Line
Monolithic architecture is not a legacy pattern to be outgrown. It is a deliberate choice that offers simplicity, speed, and reliability. For most applications—startups, internal tools, products finding market fit, teams under 50 engineers—the monolith remains the optimal architecture.
Understand its trade-offs, build with evolution in mind, and you'll have a system that serves you well for years—with clear paths to decomposition if and when the need arises.
Module Complete: You now have a comprehensive understanding of monolithic architecture—what it is, its benefits, its challenges, and when it's the right choice. In the next module, we'll explore the other side: microservices architecture and its trade-offs.
Congratulations! You've completed the Monolith Architecture module. You now understand the monolith as a serious architectural choice—not a stepping stone—with clear guidance on when to choose it. Next, we'll explore microservices to understand the other side of the trade-off.