← Back to articles

Monolith vs Microservices vs Modular Monolith: How to Choose (2026)

The microservices hype peaked around 2020. By 2026, the industry has learned hard lessons. Amazon Prime Video moved from microservices back to a monolith. Shopify runs one of the largest monoliths in the world. DHH (Basecamp/Hey) never left the monolith.

The answer isn't dogmatic — it depends on your team, your stage, and your specific constraints.

The Three Options

Monolith

A single deployable unit containing all your application code.

Strengths:

  • Simple to develop, test, deploy, and debug
  • No network calls between services (faster, more reliable)
  • Single database with transactions and joins
  • One codebase to understand
  • Easy to refactor across boundaries
  • Lower operational overhead (one thing to monitor)

Weaknesses:

  • Entire app deploys together (even for small changes)
  • Teams can step on each other in a large codebase
  • Tech stack is unified (can't use Python for ML and Go for APIs)
  • Scaling is all-or-nothing (can't scale one component independently)

Microservices

Multiple independently deployable services communicating over the network.

Strengths:

  • Independent deployment (change one service without touching others)
  • Independent scaling (scale the bottleneck, not everything)
  • Technology flexibility (right tool for each job)
  • Team autonomy (teams own their services end-to-end)
  • Fault isolation (one service failing doesn't crash everything)

Weaknesses:

  • Distributed system complexity (network failures, eventual consistency)
  • Debugging across services is hard (distributed tracing required)
  • Data consistency challenges (no cross-service transactions)
  • Operational overhead (monitoring, logging, deployment for N services)
  • Team coordination overhead (API contracts, versioning)
  • Testing is harder (integration tests require running multiple services)

Modular Monolith

A monolith with enforced module boundaries. Code is organized into independent modules but deploys as one unit.

Strengths:

  • Monolith simplicity with clean architecture
  • Module boundaries prevent spaghetti code
  • Easy to extract modules into services later (if needed)
  • Full database transactions across modules
  • Single deployment with clear ownership
  • Best of both worlds for most teams

Weaknesses:

  • Requires discipline to maintain module boundaries
  • Still deploys as one unit
  • Still one tech stack
  • Scaling is still all-or-nothing

Decision Framework

Choose a Monolith When:

  • You have <10 engineers
  • You're pre-product-market fit
  • You're iterating quickly on product direction
  • You value development speed over architectural perfection
  • Your traffic is moderate and predictable

This is most startups. Start here.

Choose a Modular Monolith When:

  • You have 10-50 engineers
  • You want clean architecture without distributed system complexity
  • You anticipate extracting services later
  • Multiple teams work on the same codebase
  • You want the option to go microservices without the commitment

This is the sweet spot for growing companies.

Choose Microservices When:

  • You have 50+ engineers across multiple teams
  • Teams need to deploy independently and frequently
  • You have genuinely different scaling requirements per component
  • You need different tech stacks for different components
  • You have the platform team to support the operational complexity
  • You're willing to invest in infrastructure (service mesh, distributed tracing, CI/CD per service)

This is large organizations. And even then, not always.

The Shopify Case Study

Shopify serves millions of merchants with one of the largest Ruby on Rails monoliths ever. Their approach:

  1. Started as a monolith (obviously)
  2. Grew the monolith as the company scaled
  3. Introduced component boundaries within the monolith
  4. Enforced module isolation using Packwerk (a Ruby gem they built)
  5. Result: 300+ developers working on the same monolith with clear ownership and boundaries

They never needed microservices. The modular monolith approach works at massive scale.

The Amazon Prime Video Case Study

In 2023, Amazon Prime Video's monitoring team moved from microservices (AWS Step Functions + Lambda) back to a monolith. The result:

  • 90% cost reduction
  • Simpler architecture
  • Easier debugging
  • Better performance

Their microservices architecture was over-engineered for the workload. The network overhead and orchestration complexity weren't justified.

Common Mistakes

Mistake 1: Starting with Microservices

"We'll need to scale eventually, so let's build microservices from the start."

Reality: You'll spend 70% of your time on infrastructure instead of product. Most startups fail because they don't find product-market fit, not because they can't scale.

Mistake 2: Premature Decomposition

"Let's split into services now while the codebase is small and clean."

Reality: You don't yet know where the boundaries should be. Splitting too early creates the wrong service boundaries, which are expensive to fix.

Mistake 3: Microservices for Team Independence

"Teams keep stepping on each other in the monolith."

Reality: This is usually a codebase organization problem, not an architecture problem. A modular monolith with clear ownership achieves the same team independence.

Mistake 4: Ignoring the Operational Cost

"We'll just add Kubernetes and service mesh."

Reality: Microservices require: service discovery, distributed tracing, centralized logging, container orchestration, API gateway, circuit breakers, health checks per service, independent CI/CD per service, and on-call for each service. This is a full-time platform team's job.

Implementation Guide

Building a Modular Monolith (Recommended Default)

Step 1: Define Modules by Domain

src/
  modules/
    billing/        # Stripe integration, invoices, subscriptions
    auth/           # Authentication, authorization, sessions  
    users/          # User profiles, preferences, teams
    notifications/  # Email, push, in-app notifications
    analytics/      # Event tracking, reporting
  shared/           # Shared utilities, common types

Step 2: Enforce Boundaries

  • Each module exposes a public API (exported functions/types)
  • Modules can only import from other modules' public APIs
  • No direct database access across modules
  • Use dependency injection or event bus for inter-module communication
  • Lint rules to enforce import boundaries

Step 3: Module Communication

  • Synchronous: Direct function calls through public module APIs
  • Asynchronous: In-process event bus for loose coupling
  • Database: Each module owns its tables (even in the same database)

Step 4: Extract When Needed If a module genuinely needs independent scaling or a different tech stack, extract it into a service. The clean boundaries make this straightforward.

When Microservices Make Sense: Real Examples

  • Real-time communication (chat, video) has different scaling patterns than CRUD APIs
  • ML/AI inference needs GPU hardware that your web servers don't
  • Payment processing has strict security/compliance requirements worth isolating
  • Third-party integrations that are unreliable and shouldn't crash your main app
  • Background processing at massive scale (millions of jobs/day)

These are specific, justified extractions — not "let's split everything into services."

FAQ

At what scale should I switch to microservices?

There's no magic number. Shopify runs a monolith with 300+ developers. The question isn't scale — it's whether you have specific, unsolvable problems that microservices address.

Can a monolith handle millions of users?

Yes. Shopify, Basecamp, GitHub (initially), Stack Overflow (still), and many others serve millions of users with monolithic architectures. Scaling a monolith means scaling horizontally (multiple instances behind a load balancer) and optimizing database queries.

What about serverless? Is that a fourth option?

Serverless is a deployment model, not an architecture. You can deploy a monolith to serverless (SST, Serverless Framework) or deploy microservices as serverless functions. The monolith vs. microservices decision is about code organization and team structure.

How do I migrate from a monolith to microservices?

The Strangler Fig pattern: extract one module at a time behind an API gateway. Route traffic to the new service, keep the old code running, switch over gradually. But first, ask yourself: do I actually need to?

My CTO insists on microservices. How do I push back?

Ask: "What specific problem will microservices solve that a modular monolith can't?" If the answer is vague ("scalability," "best practices"), suggest a modular monolith as a compromise. If there's a specific, concrete problem (genuinely different scaling requirements), microservices may be justified.

The Verdict

For 95% of software projects in 2026:

  1. Start with a monolith (simple, fast, iterate quickly)
  2. Evolve to a modular monolith as the team and codebase grow
  3. Extract specific services only when you have concrete problems that require it

The best architecture is the one that lets you ship product and iterate fastest at your current scale. That's almost always a monolith.

Get AI tool guides in your inbox

Weekly deep-dives on the best AI coding tools, automation platforms, and productivity software.