Monolith#
A monolith is a single deployable unit containing the whole application. One repo, one build, one runtime, one database (usually).
flowchart LR
client([Client]) --> LB[Load Balancer]
subgraph Fleet[Horizontally-scaled fleet]
A[Instance 1]
B[Instance 2]
C[Instance 3]
end
subgraph Inside[Each instance: one artifact]
UI[UI / API]
Auth[Auth]
Billing[Billing]
Catalog[Catalog]
Orders[Orders]
UI --- Auth
UI --- Billing
UI --- Catalog
UI --- Orders
end
LB --> A
LB --> B
LB --> C
A --> DB[(Database)]
B --> DB
C --> DB
Structure#
What a monolith looks like in practice. The defining feature is that all the code runs together in one process at a time; a function call across modules stays a function call, never becomes a network call. The rest of the properties follow from that.
Code lives in one repository.
CI produces one artifact (a binary, container image, or deployable bundle).
All code runs in one process at a time, with one set of in-memory state.
All requests cross the same boundary; an internal call is a function call, not a network call.
Strengths#
What the monolith does better than any distributed alternative. Most of these come down to “everything is one thing”; one deploy, one transaction, one shared memory, one set of logs. Operational simplicity is the largest under-counted advantage.
Cheap to build, no cross-service plumbing.
Cheap to run, one thing to deploy, monitor, and patch.
Atomic refactoring, rename a function in one PR; no version skew.
Strong consistency by default, one database, one transaction.
Low operational surface area, the simplest version that works in production.
Weaknesses#
The costs of having one thing. Most of them only bite at scale (tens of engineers, multi-minute builds, mismatched scaling profiles). Below that scale, the weaknesses are theoretical and the strengths are real.
Single language / runtime, everyone shares it.
Deploys are all-or-nothing, a bug in one feature delays the next.
Scales as a unit, if one endpoint is hot, you scale the whole thing.
Build times grow, a 30-minute build slows feedback for every contributor.
Coordination cost grows with team size, many hands on the same artifact.
The Modular Monolith#
The under-rated middle ground. One deployable, but with strong internal boundaries (clear modules, defined APIs between them, and a database schema per module wherever feasible). Build-time enforcement of the boundaries is what keeps it from sliding back into a ball of mud.
One deployable, but strong internal boundaries; clear modules, defined APIs between them, and, ideally, a database schema per module.
Cross-module access goes through declared interfaces, not direct calls into another module’s internals.
Build-time enforcement of boundaries (package privacy, allowed-imports rules, dependency-cruiser, archunit, modular linting).
Why it’s worth doing.
Captures most of the operational simplicity of a monolith.
Sets up clean future splits if specific modules need independent scaling or release.
Forces interface design earlier, when it’s cheap.
When to Pick a Monolith#
Almost any time you don’t have a specific reason to do something else. The well-architected monolith remains the most under- appreciated style in 2026 tech; most teams reach for microservices on instinct and then spend years rebuilding the operational simplicity they gave up.
New product with uncertain requirements, what you build first will be wrong; cheap iteration matters more than scaling for years.
Small team, under ~20 engineers, the coordination overhead of microservices outweighs the benefits.
Tight transactional consistency, billing, inventory, anything with hard correctness boundaries.
The well-architected monolith remains the most under-appreciated style in 2026 tech.
When to Move Off#
The signals that a monolith is genuinely hurting, not just making someone uncomfortable. Each one is observable in metrics the team already collects (deploy frequency, build time, resource profile, compliance scope). False signals are listed afterwards because they come up so often.
Deploy frequency is bottlenecked by mutual interference between teams.
CI / build times are unsustainable.
Specific subsystems have wildly different scaling profiles (one is CPU-heavy, one is RAM-heavy, one is I/O-bound).
Compliance scope (PCI, HIPAA) requires a smaller blast radius for some components.
False signals (don’t move off the monolith for these).
“Microservices are modern.”
“We want to use a different language for one feature.”
“We’re going to be huge someday.”
Operational Concerns#
The day-to-day operations details that bite a monolith specifically. Shared database, shared log stream, vertical scaling first, and, importantly, background work that lives inside the same process at small scale and graduates to its own worker pool as load grows.
Database is usually shared, migrations affect every feature; expand-and-contract.
One log stream, structured logging matters; per-module fields.
Scaling vertically first, monoliths benefit more than most architectures from a bigger machine.
Read replicas for read-heavy load.
Background jobs, run in the same process at small scale; in a separate worker process at medium scale; in a queue + worker pool at larger scale (still a monolith).
Migration to Modular Monolith#
If a monolith is becoming a “ball of mud” (internal boundaries dissolved, every change touching every module), modular monolith is the destination, not microservices. The five-step migration below is years of work, not weeks; treat it as a long-running cleanup, not a project.
Identify modules from the codebase.
Define interfaces; force inter-module calls through them.
Add build-time enforcement (allowed-imports tests).
Migrate per-module data into per-module schemas.
Iterate; this is years of work, not weeks.
A modular monolith is a much better destination than a “distributed monolith” of microservices that still must deploy together.