Few topics in software architecture cause as much confusion as monoliths and microservices. The words show up in job listings, in conference talks, and in that one senior engineer's plan to "break the monolith apart" this quarter. Somewhere along the way the two words stopped describing a technical choice and started sounding like a moral one, where the monolith is the mess you inherited and microservices are the clean future you're supposed to want.

Most of that framing is wrong, and it makes the decision harder than it needs to be.

The honest answer is that neither one is better. They are simply two different ways of building and deploying an application. Each one solves certain problems while introducing others.

Once you understand those trade-offs, the discussion becomes much simpler. You can look at the problems you're trying to solve and choose the approach that fits best.

The one question that actually separates them

Forget the debate for a moment. The real difference comes down to one simple question:

"When you deploy your application, are you deploying one unit or many?"

A monolith is one deployable unit. All of your features live in one codebase, they are built together, and they ship together as a single running program. Microservices split that same application into many small programs, each one built and deployed on its own, each one responsible for a slice of the whole.

That is the entire distinction. Not code quality, not team size, not how modern the stack is. It is a question of how many independent pieces you deploy. Everything else people argue about follows from that one fact, so it is worth holding onto as we go.

What a monolith actually looks like

Picture a typical online store. It has user accounts, a product catalog, a shopping cart, an order system, and payments. In a monolith, all five of those live in the same codebase and run as one process.

        Online store (one deploy)
        ┌───────────────────────┐
        │  Users                │
        │  Products             │
        │  Cart                 │
        │  Orders               │
        │  Payments             │
        └───────────┬───────────┘

              One database

When the cart calls the product catalog to check a price, that is a normal function call inside the same program. The data all lives in one database, so an order can reference a user and a product with a simple join. To deploy, you build the whole thing once and start it on a server. If you need more capacity, you run several copies of that same program behind a load balancer.

This is how most applications start, and for good reason. Everything is in one place, so the code is easy to run on your laptop, easy to test end to end, and easy to reason about because a single request travels through one program you can step through. The monolith gets a bad reputation, but a huge number of successful products run happily as one well organized codebase for years.

As applications and teams grew, deploying one large application for every change became increasingly painful. Developers started looking for ways to split large systems into smaller, independently deployable pieces.

What microservices actually look like

Now take that same store and split it into smaller, independent pieces. Users become one service. Products become another. Cart, orders, and payments each become their own service, deployed independently and often backed by their own database.

                      network calls
    ┌───────────┬───────────┬──────────┬────────────┐
    │           │           │          │            │
┌───┴───┐  ┌────┴─────┐  ┌──┴───┐  ┌───┴────┐  ┌────┴─────┐
│ Users │  │ Products │  │ Cart │  │ Orders │  │ Payments │
└───┬───┘  └────┬─────┘  └──┬───┘  └───┬────┘  └────┬─────┘
    ↓           ↓           ↓          ↓            ↓
  own db      own db      own db     own db       own db

The features are the same, but the wiring changed completely. When the cart needs a product price, it no longer makes a function call. It makes a network request to the products service and waits for a response over HTTP or a message queue. When orders needs to know who a user is, it asks the users service instead of joining a table.

The biggest advantage is independence. The payments team can deploy a fix at 2pm without touching the catalog. If checkout traffic spikes, you can run twenty copies of the orders service while leaving everything else at one copy each.

                  Before

  ┌──────────┐   ┌──────────┐   ┌──────────┐
  │  Orders  │   │ Products │   │ Payments │
  └──────────┘   └──────────┘   └──────────┘
       ×1             ×1             ×1


           Checkout traffic spikes

  ┌──────────┐   ┌──────────┐   ┌──────────┐
  │  Orders  │   │ Products │   │ Payments │
  ├──────────┤   └──────────┘   └──────────┘
  │  Orders  │        ×1             ×1
  ├──────────┤
  │  Orders  │   only the service under load
  ├──────────┤   scales, the rest stay at ×1
  │  Orders  │
  ├──────────┤
  │  Orders  │
  └──────────┘
       ×5

Interestingly, software often ends up reflecting the way teams are organized, an observation known as Conway's Law.

Conway's Law: Organizations design systems that mirror the way they communicate.

Melvin Conway, 1967

A company with separate payments, catalog, and orders teams will often build separate services for those areas too.

Teams can even choose different languages or databases per service, because the only contract between them is the network interface they agree on.

Before we go deeper, it helps to see the two approaches side by side. This is the same story the rest of the article tells, condensed into a single view.

Monolith Microservices
Deployable units One application Many small services
How code talks Direct function calls Requests over the network
Data Usually one shared database Often one database per service
Scaling Run more copies of the whole app Scale each service on its own
Main cost Everything ships together Network failures and data spread across services
Best fit A single team shipping one product Many teams with very different needs

The part that confused me most

For a long time, I thought "monolith" was just a polite word for messy code and "microservices" meant clean, well-designed software. I also thought that if you split an application into separate projects or folders, such as client, server, db, and shared, you had somehow built microservices.

Both ideas are wrong.

A monolith can be beautifully organized, with clear internal modules that barely know about each other. People often call this a modular monolith. It gives you much of the structure people associate with microservices while still deploying as a single application.

The opposite is also true. Microservices can be an absolute mess. If changing one service forces you to redeploy three others every time, you haven't really gained the independence microservices are supposed to provide. You've simply built a distributed monolith.

So what's the mental rule?

Microservices aren't about how many repos or modules you have. They're about how many applications you deploy independently.

Everything else is just how you organize your code.

Once that clicked for me, the whole discussion became much simpler. Code organization and deployment are two different concerns. You can have clean or messy code in either architecture.

A real-world comparison

So far we've looked at the structure of each architecture. Now let's see how that affects everyday development.

Imagine your product manager asks for a new feature: support for discount codes at checkout.

Monolith version

In a monolith, adding the feature is straightforward. You create a discounts module, store the discount codes in the application's database, and have the orders code call it when calculating the total.

Because everything runs inside the same application, you can test the entire checkout flow on your machine. When you're ready, you deploy the application once, and the feature is available everywhere.

Microservices version

In a microservices architecture, the change is usually larger. You might create a new discounts service with its own database and deploy it independently. During checkout, the orders service now has to call the discounts service over the network to calculate the final price. That immediately raises new questions. What happens if the discounts service is slow? What if it is temporarily unavailable? Should the order continue without the discount, or should checkout fail?

Testing also changes. Instead of running one application, you now need several services working together. On the other hand, the discounts service can evolve independently. The team responsible for discounts can deploy improvements without coordinating a release of the rest of the application.

Neither approach is inherently better. The monolith makes the feature easier to build. Microservices make it easier to own, evolve, and deploy independently. That's the trade-off in its simplest form.

The price of independence

Microservices are not a free upgrade. The independence they give you comes with extra complexity.

Inside a monolith, one part of the application talks to another through a simple function call. It is fast and rarely fails. In a microservices architecture, that same communication happens over the network. Network requests can be slow, fail completely, or return unexpected errors, so every service has to be prepared for those situations.

Data becomes more complicated too. In a monolith, everything usually lives in one database, so updating related information is straightforward. In a microservices architecture, each service often has its own database. Keeping data consistent across several independent systems is much harder.

Debugging also changes. Instead of following a request through one application, you now have to trace it across several services. That's why teams using microservices rely heavily on logging, monitoring, and distributed tracing to understand what is happening.

None of this means microservices are a bad idea. It simply means that the independence they provide comes at a cost. If that independence solves problems your team actually has, the extra complexity is worth it. If not, a monolith is often the simpler and better choice.

Monolith or microservices: which should you choose?

For most new applications, a modular monolith is the sensible starting point. It lets you focus on building the product instead of managing infrastructure. Development, testing, and deployment stay simple, and if you keep the code well organized, you can still split parts of the application later if the need arises.

Microservices start to make sense when one deployable application is no longer enough. That usually happens because of growth rather than technology. Multiple teams need to release independently, different parts of the system have very different scaling requirements, or a particular service needs to be isolated for reliability.

In other words, the signal isn't that your monolith feels old. The signal is that your organization or your application has outgrown a single deployment.

That's why many successful systems follow the same path. They start as a monolith, learn where the natural boundaries are, and only then split out the parts that genuinely benefit from becoming independent services. Starting with dozens of microservices before you have that pressure often gives you the costs long before you see the benefits.

Beyond monoliths and microservices

Monoliths and microservices are the two architectures you'll hear about most often, but they aren't the only options. As systems grow, developers have come up with other ways of splitting applications and communicating between them.

Serverless. Instead of running long-lived services, you deploy small functions that run only when they are needed. Platforms such as AWS Lambda and Cloudflare Workers start them on demand, scale them automatically, and stop them when they are no longer needed.

Event-Driven Architecture. Instead of one service calling another directly over HTTP, services communicate by publishing and receiving events. A payment might publish an "Order Paid" event, and other services react to it whenever they need to.

Although they look different, they all explore the same basic idea: how should an application be split into independent pieces, and how should those pieces communicate?

Key takeaways

  • The real difference between a monolith and microservices is the number of independently deployable units, not the quality of the code.
  • A monolith ships as one program with usually one database, which keeps development, testing, and reasoning simple.
  • Microservices split an application into many small programs that deploy independently and talk over the network, trading simplicity for independence.

Frequently asked questions

What is the main difference between a monolith and microservices?

A monolith is one application that you build and deploy as a single unit, while microservices split that same application into many small services that deploy independently and talk over the network. Everything else, including how you organize the code, follows from that one difference.

Is a monolith bad or outdated?

No. A monolith is simply one deployable unit, and plenty of large, successful products run as well organized monoliths.

Are microservices always more scalable?

Not automatically. Microservices let you scale individual pieces independently, which helps when different parts have very different load. But you can also scale a monolith by running more copies of it.

What is a modular monolith?

A modular monolith is simply a monolith with clear internal boundaries between features. The code is organized into separate modules, but everything is still built and deployed as a single application.

For example, a project might be organized like this:

my-app/

├── src/
│   ├── users/
│   ├── products/
│   ├── orders/
│   ├── payments/
│   └── shared/

├── database/
├── tests/
└── package.json

Even though the code is split into well-defined modules, this is still a monolith because the entire application is built and deployed together. You get much of the organization people associate with microservices without the cost of distributing everything across the network.

Should a new project start with microservices?

Usually not. Most new projects are better off as a monolith, ideally a modular one, because you rarely know the right service boundaries up front. It is common and healthy to start with a monolith and split out services later, once real scaling or team pressure shows you where the seams are.

How do microservices talk to each other?

Over the network, most often through HTTP APIs or a message queue, instead of the direct function calls a monolith uses internally. That network hop is exactly what gives services their independence, and also what introduces the extra latency and failure cases you have to plan for.

The mental model to remember

Monoliths and microservices are not rival teams you have to pick a side on. They are two different ways of building the same application, and the right choice depends on your scale, your team, and the problems you actually have today, not the ones you might have someday.

Once you stop thinking in terms of "good" and "bad" and start thinking in terms of deployments, the whole discussion becomes much simpler. You can look at your own system, decide how much independence each part really needs, weigh that against the extra complexity, and make the decision on its merits.

If you remember just one thing from this article, let it be this:

A monolith is one independently deployable application. Microservices are many independently deployable applications that communicate over the network.

Everything else is just a consequence of that one design choice.