All insightsArchitecture

Microservices vs monolith: how to choose

Microservices vs monolith compared on team autonomy, operational cost and failure modes. Why most teams should start with a modular monolith and split later.

9 min read

Microservices vs monolith is one of the oldest arguments in software architecture, and it is usually framed as a choice between something old and something modern. That framing is wrong. The honest answer is that most teams should start with a well-structured modular monolith and split out services only when they have a concrete reason to. This guide explains the two ends of the spectrum, the middle ground, and the real trade-offs that should drive the decision.

The three options, defined plainly

The monolith

A monolith is an application that you build, test and deploy as a single unit. The code lives in one place and runs in one process, so modules call each other directly in memory. That makes a monolith easy to develop, easy to run locally, and easy to reason about end to end. The bad reputation monoliths carry usually comes from the unstructured kind, where everything reaches into everything else and no boundaries exist. That is a design problem, not a property of monoliths as such.

Microservices

Microservices break the application into small services that are deployed independently. Each service owns a slice of the domain and its own data, and services talk over the network through APIs or messages. The promise is autonomy: separate teams can release their service on their own schedule, and you can scale a busy service without scaling everything else. The price is that you are now running a distributed system, with all the operational weight that brings.

The modular monolith

A modular monolith is the middle ground that gets skipped too often. It is still a single deployable application, but it is organised into clear internal modules with explicit boundaries and minimal coupling. You keep in-process calls, one deployment and simple local testing, while keeping the seams clean enough that pulling a module out into its own service later is cheap. In practice this is the right default for most products, because it preserves your options without paying for distribution up front.

The real trade-offs

The decision is not about fashion. It is about which set of problems you would rather have. Below are the trade-offs that actually matter once the system is live.

What microservices buy you

  • Team autonomy. Independent teams can own a service end to end and release without coordinating a single shared deploy. This matters most when you have several teams large enough to step on each other.
  • Independent deployability. A small change to one service ships on its own, so release cadences can differ across the system rather than moving in lockstep.
  • Targeted scaling. You can give more resource to a hotspot, such as a search or pricing service, without scaling the whole application to match its busiest part.
  • Failure isolation. With careful design, one service degrading does not have to take the rest down, which is valuable for parts of a system with very different reliability needs.

What microservices cost you

  • Operational complexity. You now run many deployable units, each needing build pipelines, monitoring, logging, tracing and on-call. The platform work to support this is real and ongoing.
  • Distributed-systems failure modes. Network calls fail, time out and retry. You inherit partial failures, retries, idempotency, circuit breakers and cascading outages that simply do not exist when modules call each other in memory.
  • Data consistency. Splitting data across services means giving up easy transactions. You move to eventual consistency, sagas and compensating actions, which are harder to design and harder to debug.
  • Latency and cost. Every network hop adds latency, and chatty service-to-service traffic adds up. More moving parts and more infrastructure usually means a higher bill and more to keep running.

Notice that almost everything on the cost side is the price of distribution itself. If you do not need the benefits, you are paying that price for nothing. That is the core of the argument.

Start with a modular monolith

For most teams, the right move is to start with a single application, invest in clean internal module boundaries, and resist splitting until something forces your hand. You get fast local development, simple deployments, real transactions and one place to look when something breaks. Crucially, if you keep the boundaries honest, you keep the option to extract a service later at low cost. You lose almost nothing by waiting, and you avoid carrying distributed-systems overhead before you have a reason for it.

The trap to avoid is the unstructured monolith, where modules leak into each other until no boundary survives. That is what gives monoliths a bad name, and it is also what makes a future split painful. The discipline you put into module boundaries now is the same discipline that makes microservices viable later, so the work is never wasted.

The signals that justify a split

Extract a service when you have a concrete driver, not a general feeling that microservices are more modern. The clearest signals are these.

  • A scaling hotspot. One part of the system has a load profile so different from the rest that scaling it separately would genuinely help. That is a reason to pull it out, and only it.
  • Team boundaries. You have grown to several teams that keep colliding in one codebase and one deploy queue. Splitting along team-owned domains lets them move independently.
  • Differing release cadences. One area needs to ship many times a day while another changes rarely and must stay stable. Separating them stops the slow part from gating the fast part.
  • Independent failure or compliance boundaries. A component needs its own reliability, security or data-residency posture that is hard to honour inside a shared application.

Rule of thumb

Start with a modular monolith. Keep the internal boundaries clean. Extract a service only when you can name the specific driver, a scaling hotspot, a team boundary or a different release cadence, that the split solves. If you cannot name the driver, you are buying distributed-systems complexity you do not need yet.

How to split, when the time comes

When a driver does appear, split incrementally rather than rewriting the whole system at once. The strangler pattern is the safe path: stand up the new service alongside the monolith, route the relevant traffic to it, and retire the old module once the new one is proven. We cover this approach in more detail in our guide to modernising legacy systems. If the goal is composability across the front end as well as the back, the same thinking carries into a MACH architecture, where microservices, APIs and a headless front end let each part change on its own schedule.

Whatever the shape, microservices only work if the platform underneath them is ready. Deployment pipelines, observability, service templates and paved paths are what stop a fleet of services becoming a fleet of pager alerts. That groundwork is the subject of our platform engineering guide, and it is part of how we build digital platforms that teams can actually run.

A split done for a real reason

We re-architected the platform for Motive Partners from a tightly coupled system to an event-driven microservices design. That work was not change for its own sake. It was driven by concrete needs: readiness for acquisition and onboarding a first bank client, which demanded independent scaling, clear service boundaries and the ability to evolve parts of the platform separately. The split paid for its complexity because there were specific outcomes that depended on it. That is the test to apply to any move toward microservices.

So the answer to microservices vs monolith is rarely one or the other on day one. Start simple, keep your boundaries clean, and let real pressure tell you when and where to split. If you are weighing this decision for your own platform, see how we approach digital platforms.

Frequently asked questions

What is a monolith?+

A monolith is an application built and deployed as a single unit. All the code lives in one codebase and runs in one process, so the modules call each other in memory rather than over the network. A monolith can still be well structured internally. The defining trait is that you build, test and release it as one thing.

What are microservices?+

Microservices split an application into small, independently deployable services that each own a slice of the domain and its data. They talk to each other over the network using APIs or messages. The aim is that separate teams can build, release and scale their service on their own schedule, at the cost of running a distributed system.

Which is better, microservices or a monolith?+

Neither is better in the abstract. A monolith is simpler to build, test, deploy and reason about, which suits most teams and most products. Microservices buy team autonomy and independent scaling, but only pay off once you have a concrete driver such as a scaling hotspot, separate teams with different release cadences, or independent failure boundaries. For most teams a well-structured modular monolith is the right starting point.

What is a modular monolith?+

A modular monolith is a single deployable application organised into clear internal modules with well-defined boundaries and minimal coupling between them. You get the simplicity of one deployment and in-process calls, plus boundaries that make a later split into services cheap if you ever need one. It is the middle ground between a tangled monolith and a fleet of microservices.

Ready to build something transformative?

Talk to a product squad that delivers from day one

Whether you're launching a new product, modernising a platform or building digital capability in your teams, our cross-functional squads can help. Tell us what you're trying to achieve.