The microservices vs monolith decision comes down to one question: is your biggest problem building features or coordinating teams? A monolith is usually faster and simpler for a small team and a young product. Microservices help when many teams need to deploy and scale parts of a system independently. For many companies, a modular monolith is the best middle ground, giving clear boundaries without the cost of a distributed system.

This article compares the three options and gives concrete signals for when to switch.

What is a monolith?

A monolith is a single deployable application. All features share one codebase, one build, one process (or a set of identical processes), and usually one database. See the monolith guide.

Monoliths are often described as legacy, but that is unfair. A monolith gives you:

  • Simple local development: clone, run, debug in one process.
  • Function calls instead of network calls, so no partial failures between features.
  • Database transactions that span any tables you need.
  • One pipeline, one deployment, one set of logs.

The problems appear with growth. The codebase can turn into a tangle where every change touches everything, test suites and builds slow down, and one team's risky change blocks everyone's release. Scaling is all-or-nothing: if one feature needs more CPU, you scale the whole application.

What are microservices?

Microservices split a system into small, independently deployable services, each owning a business capability and its own data. Services communicate over the network through APIs or events. The microservices guide covers the core patterns.

The benefits are organizational as much as technical:

  • Teams own services end to end and deploy on their own schedule.
  • Each service scales independently based on its own load.
  • A failure can be contained to one service if the system is designed for it.
  • Teams can choose different technologies where it genuinely helps.

The costs are real. Every call can now fail, time out, or return stale data. You need service discovery, retries, timeouts, circuit breakers, distributed tracing, centralized logging, and a deployment platform. Transactions across services become sagas or eventual consistency. Debugging a single user request can mean following it through many services.

What is a modular monolith?

A modular monolith is a single deployable application internally divided into well-defined modules with explicit boundaries. Each module owns its domain logic and, ideally, its own tables, and other modules can only reach it through a public interface. The modular monolith guide goes deeper.

// modules/billing/index.ts - the only file other modules may import
export interface BillingApi {
  createInvoice(orderId: string, amountCents: number): Promise<{ invoiceId: string }>;
  getInvoiceStatus(invoiceId: string): Promise<"pending" | "paid" | "void">;
}

export { billingApi } from "./internal/billing-service";

// modules/orders/internal/checkout.ts
import { billingApi } from "../../billing";

export async function checkout(orderId: string, totalCents: number) {
  const { invoiceId } = await billingApi.createInvoice(orderId, totalCents);
  return { orderId, invoiceId };
}

Enforce the rule with lint rules or build tooling that forbids importing another module's internal folder. If a module later becomes a separate service, the interface is already there; you replace the in-process implementation with a network client.

This approach captures much of the design benefit of microservices, clear ownership and loose coupling, while keeping the operational simplicity of one deployable unit.

Microservices vs monolith vs modular monolith

Dimension Monolith Modular monolith Microservices
Deployment One unit One unit Many independent units
Boundaries Often implicit Explicit, enforced in code Enforced by the network
Data Shared database Shared database, owned tables Database per service
Transactions Local and simple Local and simple Sagas, eventual consistency
Scaling Whole app Whole app Per service
Operational overhead Low Low High
Team independence Low Medium High
Best fit Small teams, new products Growing teams, evolving domains Many teams, mature domains

How to choose the right architecture

Start with a monolith or modular monolith when

  • Your team is small enough to coordinate informally.
  • The domain is still changing and boundaries are unclear.
  • You do not yet have strong CI/CD, observability, and on-call practices.

Drawing service boundaries too early is expensive, because moving a boundary between services means changing APIs, data ownership, and deployments. Moving a boundary between modules is a refactor.

Choose microservices when

  • Several teams are blocked on each other's releases.
  • Parts of the system have very different scaling or reliability needs.
  • Domain boundaries are stable and well understood, often guided by domain-driven design.
  • You can invest in the platform: automated deployments, tracing, and incident response.

When to switch from monolith to microservices

Watch for these signals rather than following a trend:

  1. Deployments are frequently delayed because unrelated changes are bundled together.
  2. One component's load forces you to scale the entire application.
  3. Teams step on each other in the same code and ownership is unclear.
  4. Build and test times grow to the point that they slow everyone down.
  5. A failure in a non-critical feature regularly takes down critical flows.

If only one or two apply, a modular monolith refactor often fixes them at a fraction of the cost.

How to migrate safely

The strangler fig pattern is the standard approach. Put a routing layer in front of the monolith, extract one capability at a time into a new service, route its traffic to the new service, and remove the old code once it is stable. Good first candidates have clear boundaries, few dependencies, and a real reason to be independent, such as distinct scaling needs.

Avoid a big-bang rewrite. Extract data ownership along with code, or the new service will stay coupled to the monolith's database. And when services run in containers, orchestration becomes part of the cost; see Docker vs Kubernetes Explained.

Key takeaways

  • Monoliths are simple to build, test, and deploy, and are the right start for most new products.
  • Microservices trade operational complexity for team autonomy and independent scaling.
  • A modular monolith gives explicit boundaries without the cost of a distributed system.
  • Switch when coordination pain, scaling mismatches, or blast radius become real problems.
  • Migrate incrementally with the strangler fig pattern, moving data ownership with each service.

Frequently asked questions

Are microservices better than a monolith?

Not in general. Microservices solve scaling and team coordination problems but add network failures, distributed data, and operational overhead. For small teams and early products, a monolith or modular monolith is usually more productive.

What is the difference between a modular monolith and microservices?

Both divide a system into bounded modules. A modular monolith deploys them together in one process with a shared database, while microservices deploy each one independently and communicate over the network.

How many developers do you need before moving to microservices?

There is no fixed number. The better signal is coordination cost: when multiple teams regularly block each other's releases or ownership becomes unclear, splitting services can help.

Can you go back from microservices to a monolith?

Yes. Some teams consolidate services that are too fine-grained or always change together. Merging tightly coupled services back into a modular monolith can reduce latency, cost, and operational burden.