Skip to main content
Monolith vs Microservices for an MVP
Engineering

Monolith vs Microservices for an MVP

Microservices are the architecture founders ask for by name more than any other, usually framed as future-proofing: we'll need to scale, so let's build it properly now. It's a reasonable instinct applied to the wrong problem. Microservices are a brilliant answer to a question a five-person startup hasn't been asked yet — and the price you pay for answering it early is measured in weeks of velocity you can't get back.

Should you build your MVP as a monolith or microservices?

Build your MVP as a monolith — almost always — because microservices solve an organisational problem (many teams shipping independently) that a 2–5 person startup does not have, while charging you the full operational cost from day one. The exception is a genuinely independent workload — heavy background processing, ML inference, a real-time service with a different scaling curve — which is worth extracting early because it's a different shape of computation, not because you're anticipating growth.

The framing that clears this up: microservices are primarily a team-scaling technology, not an application-scaling one. Amazon and Netflix didn't split their systems because the code couldn't cope. They split because hundreds of engineers were tripping over each other in one deployment pipeline. Service boundaries are how you let twelve teams ship on twelve different Tuesdays without coordinating. If you have one team and one Tuesday, you've bought the cure for a disease you don't have.

What's the actual trade-off?

The two architectures diverge on nearly every operational axis, and the gap is widest exactly where an early-stage team is weakest.

| | Monolith | Microservices | |---|---|---| | Deploy complexity | One artifact, one pipeline | N pipelines, versioning, orchestration | | Team size suited | 1–15 engineers, one team | 3+ independent teams | | Data consistency | Database transactions — free | Eventual consistency, sagas, compensating writes | | Observability | A stack trace | Distributed tracing before you can debug anything | | Local dev | One command, everything runs | Docker Compose, mocks, or a shared staging environment | | Refactoring boundaries | A rename in your IDE | A coordinated multi-service migration | | Cost | One host, one database | N hosts, N databases, a gateway, a message bus | | When it wins | Small team, evolving domain | Many teams, stable boundaries, divergent scaling |

Notice that the monolith column is mostly about things you don't have to think about. That's the real asset at MVP stage. Every row in the right-hand column is a system you now own, monitor and fix at 2am — infrastructure that ships no customer-facing feature.

What do microservices actually cost you?

Four costs, in roughly the order they hurt.

Every function call becomes a network call. In a monolith, charging a customer either works or throws. Across a service boundary, that same call can time out, half-succeed, succeed but lose the response, or succeed twice because your retry fired. You now need idempotency keys, timeouts, retries with backoff, and circuit breakers — for logic that was previously three lines.

Debugging requires infrastructure you don't have. A bug in a monolith is a stack trace. A bug across six services is an investigation: which service, which version, which request. Distributed tracing (OpenTelemetry, Honeycomb, Datadog) isn't optional once you split — it's a prerequisite, and it's a week of setup plus a recurring bill before you've fixed a single defect.

You lose transactions. This is the one founders most consistently underestimate. With one database, "create the order and decrement inventory" is one transaction that either fully happens or fully doesn't. Split those across services and you're hand-rolling sagas and compensating transactions, and reasoning about the window where an order exists but the inventory change doesn't. That's a genuinely hard distributed-systems problem showing up in week three of a startup.

Boundaries get locked in before you understand the domain. At MVP stage you don't yet know what your product is. You'll learn in month four that "projects" and "workspaces" are the same concept, or that billing needs data you filed under user management. In a monolith that's a refactor your IDE mostly performs for you. Across services it's a migration, a deprecation, and a versioned API you have to support while both sides move.

The SaaS MVP tech stack we ship with says "skip microservices" for exactly this reason — this post is the long version of that one line.

When is splitting a service out the right call?

Sometimes it genuinely is, and pretending otherwise would be as dogmatic as the architecture-astronaut position. The test isn't "will this grow?" — everything grows. The test is: does this workload have a fundamentally different runtime shape from your web requests? Three cases pass.

Heavy background processing. Video transcoding, large report generation, bulk imports, document pipelines. These run for minutes, not milliseconds, and they don't fit inside serverless function timeouts. They also need to fail and retry without taking a user-facing request down with them. A worker service consuming a queue is the correct design from day one — and it's a small, well-understood split with an obvious boundary.

ML inference. If you're running your own model, it wants a GPU, a different language runtime (usually Python), and a completely different deploy cadence to your web app. Forcing that into the same process means your web app now carries GPU infrastructure and a Python toolchain it never uses. Calling a hosted model API doesn't count — that's just an HTTP call from your monolith.

A separately-scaled real-time service. Persistent WebSocket connections for collaborative editing, live dashboards or chat have a memory and connection profile nothing like stateless HTTP. Ten thousand idle connections shouldn't force you to scale the whole application, and a routine deploy shouldn't drop every live session.

The common thread is that the split follows a technical necessity — different runtime, different scaling curve, different failure mode — not a guess about future org structure. One or two of these alongside a monolith is not "microservices". It's a monolith with a worker, and that's a healthy, extremely common architecture.

How do you keep a monolith from becoming a mess?

The valid critique of monoliths is real: they become big balls of mud where everything imports everything and no change is safe. But that's a discipline failure, not an inevitability — and microservices only convert it into a distributed ball of mud, which is worse, because now the spaghetti has network latency and no stack trace.

Build a modular monolith: one deployable, internally organised as if it could be split.

  • Organise by domain, not by layer. billing/, projects/, auth/ — not controllers/, services/, models/. Layer-first folders scatter one feature across the codebase; domain-first keeps a change in one place.
  • Each module exposes a deliberate public interface. One entry point per module. Other modules call that, never reach into internals.
  • No cross-module database reads. Billing doesn't query the projects table directly. It asks the projects module. This single rule does more than any other to keep a future split cheap.
  • Enforce it mechanically. ESLint import rules or dependency-cruiser turn architecture into a failing CI build rather than a code-review opinion somebody eventually stops making.
  • Treat async work as async from day one. Put slow work behind a queue interface even while it still runs in-process.

Do that and extracting a service later means moving a folder and swapping in-process calls for HTTP ones. A well-modularised monolith splits far more easily than three premature services merge back together — that direction of travel is effectively one-way, and we've watched teams pay for it.

Our default: a modular monolith

For the SaaS products we build, we ship a single Next.js application with domain-organised modules, plus a queue-backed worker when there's genuine background work. That's it.

The reasoning is deliberately non-tribal. We're not against microservices — they're the right architecture for a company with multiple teams and stable domain boundaries, and we've worked inside systems where the split was clearly correct. The argument is about sequencing. An MVP's scarcest resource is time to first paying customer, and distributed-systems overhead converts directly into less of it. You also don't yet know where your boundaries belong, so any you draw today are guesses you'll be maintaining as public API contracts tomorrow.

There's a hosting parallel worth noting: the same instinct pushes founders toward Kubernetes early, when managed platforms carry the ops load for free at this scale — we weigh that trade-off in Vercel vs AWS. And this decision is largely independent of language: the modular-monolith argument holds whether you land on Django or Node.js.

To be explicit about the cost we accept: a monolith means one deploy pipeline everyone shares, so a bad deploy affects everything, and one runaway query can degrade the whole app. Those risks are real. At five engineers they're far cheaper than the alternative, and decent CI plus feature flags mitigates most of what's left.

Frequently asked questions

Can you migrate from a monolith to microservices later?

Yes — and it's considerably easier than the reverse, which is the entire argument for starting monolithic. The standard path is the strangler-fig pattern: identify a module with clean boundaries, stand it up as a service, route traffic to it, then delete the old code path. If you've kept modules domain-organised with no cross-module database access, extraction is mostly mechanical. Teams that started with premature microservices and need to consolidate face a much uglier job, because merging services means reconciling separate databases, duplicated models, and inconsistent data that eventual consistency quietly allowed to accumulate.

At what point do you actually need microservices?

Usually when your engineering team passes roughly 15 to 20 people organised into genuinely independent teams — the trigger is coordination cost, not user count. The signal is organisational: multiple teams blocking each other in one deploy pipeline, release trains forming, features waiting on unrelated work to stabilise. Plenty of products serve hundreds of thousands of users from a monolith very comfortably — Shopify and Basecamp are well-documented examples. If your deploys aren't contended and one person can still hold the system in their head, you're not there yet.

Will investors or a technical reviewer judge a monolithic MVP?

A competent technical reviewer will judge premature microservices far more harshly than a clean monolith. Experienced engineers read early over-architecture as poor judgement about trade-offs — scarce resources spent on infrastructure instead of product. What technical due diligence actually probes is whether the code is comprehensible, tested and extensible, and whether your team can ship. A tidy modular monolith with clear domain boundaries answers all three. Six services with duplicated auth logic and no distributed tracing answers none of them.

Isn't a monolith a scaling risk?

Scaling limits at MVP stage come from your database and your queries, not your architecture. A monolith scales horizontally perfectly well — run more instances behind a load balancer, since the application layer is usually stateless. The ceiling you hit first is almost always a missing index, an N+1 query, or an unbounded read, and splitting into services fixes none of those while adding network latency to every call. Fix the database, cache what's hot, move slow work onto a queue. That takes you a very long way past the point where architecture becomes the binding constraint.

The bottom line

Ask one question: how many independent teams need to deploy without coordinating? If the answer is one, build a monolith — modular, domain-organised, with a queue-backed worker for anything genuinely slow. If a specific workload has a different runtime shape (background processing, ML inference, real-time connections), split that one thing out and keep the rest together.

What shouldn't decide it: anticipated scale, what a hyperscaler's engineering blog describes, or how the architecture diagram will look in a pitch deck. Architecture is a means to shipping something customers want, and premature distribution is one of the more expensive ways to delay that — a recurring theme in how long an MVP takes to build.

Want an architecture sized to your actual team?

The right answer depends on your team, your workloads and your timeline — not on a default. If you want an architecture scoped around what you're really building, including an honest read on which services (if any) are worth splitting out on day one, book a free scoping call. We'll map it out and quote it fixed-price, so the number is knowable before you commit.

Sameer AhmadCo-Founder & CTO, Coderacle

Sameer is the co-founder and CTO of Coderacle, a London software studio building SaaS MVPs for UK founders. He leads engineering and architecture on every build — stack decisions, scalable foundations, and getting products to production without the usual rewrites.

Leave a comment