Microservices vs Monolith: How to Choose
Monoliths ship faster early; microservices buy independent scaling and team autonomy at the cost of distributed complexity. How to choose.
The debate between microservices and monolithic architectures is one of the most reliably heated in software engineering — and it is often framed as a question of which is better. That framing is wrong. The real question is which fits your current team, codebase, and growth trajectory. Both architectures power successful products. The differences are about tradeoffs, and those tradeoffs shift over time.
What is a monolith?
A monolith is a single deployable application. All the code — user authentication, billing, notifications, the API, background jobs — lives in one codebase and is deployed as one unit. When you ship a change, you deploy the whole application.
That is often exactly the right model:
- Simple to develop locally. One process to start, one codebase to navigate, one test suite to run.
- Easy to refactor. Moving a function across modules is a single commit. There are no API contracts to version between services.
- Cheap to operate. No inter-service networking, no distributed tracing setup, no service mesh.
- Fast to ship early. A small team building v1 does not want to maintain 12 services.
The risks emerge as the codebase grows. Without discipline, a monolith becomes a big ball of mud: tightly coupled modules where changing anything touches everything, long test cycles, and deployments that feel dangerous. Scaling is also coarser — if only your image-processing code needs more CPU, you still scale the whole application.
What are microservices?
Microservices is an architecture where an application is composed of many small, independently deployable services that communicate over a network — typically via REST, gRPC, or a message broker like Kafka. Each service owns its own data store and can be deployed, scaled, and rewritten independently.
The payoff:
- Independent scaling. Scale the checkout service without scaling the recommendation engine.
- Team autonomy. Each team owns a service end-to-end, deploys on their own schedule, and can choose their own technology stack.
- Fault isolation. A bug in the notification service does not crash the entire application.
- Incremental rewrites. You can migrate one service to a new language or framework without touching the rest.
The cost is real. Microservices introduce distributed-systems complexity: network calls fail and add latency; you need Kubernetes or equivalent orchestration; distributed tracing and centralized logging become mandatory; data consistency across service boundaries is hard; and each new service is operational overhead. A team of five building their first product should not start here.
Comparison at a glance
| Monolith | Microservices | |
|---|---|---|
| Deployment unit | One application | Many independent services |
| Local development | Simple | Complex (service mesh or stubs) |
| Scaling granularity | Coarse (whole app) | Fine (per service) |
| Team independence | Low | High |
| Operational overhead | Low | High |
| Refactoring | Easy across modules | Hard across service boundaries |
| Fault isolation | Low | High |
| Best for | Early stage, small teams | Large orgs, high scale |
Conway’s law
Conway’s law is the observation that organizations tend to produce systems that mirror their communication structures. If you have three backend teams, you will likely end up with three backend subsystems — whether or not that is the architecture you planned for. Microservices architectures make Conway’s law explicit: each team’s service boundary is intended to match a team boundary. This is one reason microservices tend to work better at companies with clearly defined, autonomous engineering teams and struggle at smaller orgs where everyone is in everyone else’s code anyway.
Start with a modular monolith
The most practical guidance for teams that are not yet at microservices scale: build a modular monolith. Define clear internal module boundaries — billing/, auth/, notifications/ — enforce them with code review or automated rules, and treat each module as if it were a service (its own directory, its own tests, well-defined interfaces). This gives you the simplicity of a monolith while making future extraction much easier.
When you later split out a service, you are extracting a module with clean boundaries, not untangling spaghetti. An internal developer platform can reduce the operational burden of that split by standardizing how services are deployed and observed.
When to split a service out
Concrete signals that a module is ready to become a service:
- It has a clearly different scaling requirement than the rest of the application.
- A separate team owns it and its deployment cadence conflicts with the monolith’s.
- It has a reliability requirement that the rest of the app does not (say, a payment processor that must stay up even if the marketing pages go down).
- It needs a fundamentally different technology — a machine-learning model server, a high-throughput event processor, a load balancer fronting stateful connections.
What about APIs?
Microservices expose their functionality through APIs. In practice this means every service-to-service call is now a network call with all the failure modes that implies: timeouts, retries, eventual consistency, and the need for circuit breakers. A monolith’s in-process function call, by contrast, is microseconds and never fails due to a bad network packet. This is not a reason to avoid microservices — it is a reason to understand what you are signing up for.
The takeaway
Start with a monolith — likely a modular one — and extract services when the organizational or scaling pressure is concrete, not hypothetical. Microservices are not a destination to reach; they are a tradeoff to accept when the benefits (independent scaling, team autonomy, fault isolation) outweigh the costs (distributed complexity, operational overhead). The teams that succeed with microservices typically have well-defined team structures, mature DevOps practices, and clear service ownership. The teams that struggle usually adopted the pattern before they needed it.
Tagged
Keep reading
Chisato · · 3 min read What Is a Runbook? Incident Response Playbooks
A runbook is a step-by-step document for handling a specific operational task or incident, turning tribal knowledge into a repeatable procedure.
Chisato · · 4 min read Kubernetes ConfigMaps vs Secrets: What's the Difference
ConfigMaps store non-sensitive configuration; Secrets store credentials with base64 encoding and tighter access controls. When to use each.
Chisato · · 4 min read What Is a NAT Gateway?
A NAT gateway lets private-subnet resources reach the internet outbound while staying unreachable from it, translating private IPs to a public one.