Architecture Styles Simplicity

Monolith Architecture

A single deployable unit where all application components run in one process, sharing memory and a common data store.

⏱ 7 min read

What it is

A monolithic architecture packages all application functionality — UI, business logic, and data access — into a single deployable artifact that runs as one process. All modules share the same memory space, call each other through in-process function calls, and typically use a single relational database. This is the default architecture that emerges naturally when starting a new application without deliberate architectural intent. Monoliths are not inherently bad; they are simply architectures where the unit of deployment equals the entire application. The "big ball of mud" anti-pattern is a degenerate form, but a well-structured monolith is a legitimate, often superior choice for many organizations.

Why it exists

Monoliths exist because they are the path of least resistance when building software. There are no network hops between components, no distributed transaction complexity, no service discovery infrastructure to maintain. A developer can run the entire system locally with a single command and debug it with a standard IDE debugger. For teams smaller than roughly two pizza teams (10–15 engineers), the coordination overhead of microservices typically outweighs any benefit. Many successful large-scale systems — Stack Overflow, Basecamp, Shopify — run on monoliths or have returned to them after experiencing microservices complexity. The monolith deserves rehabilitation as a first-class architecture choice.

When to use

  • Team size is small (under ~15 engineers) and cross-team coordination is not yet a bottleneck.
  • The domain is not yet well understood — a monolith lets you refactor boundaries cheaply before they become service contracts.
  • Deployment simplicity is a priority: one artifact, one deployment pipeline, one monitoring target.
  • Low operational maturity — the team lacks container orchestration, service mesh, or distributed tracing experience.
  • The application has uniform scaling requirements — no single component needs to scale independently.
  • You are building an MVP and want to validate the business before investing in infrastructure complexity.

When not to use

  • Multiple autonomous teams need to deploy independently without coordinating releases.
  • Individual components have dramatically different scaling requirements (e.g., a CPU-intensive image processing pipeline alongside a transactional order service).
  • Technology heterogeneity is required — different components need different languages or runtimes.
  • Regulatory isolation demands that sensitive subsystems run in separate trust zones with network-level separation.

Typical architecture

A monolith typically organizes code into horizontal layers (presentation, business logic, data access) or vertical slices by feature. All code compiles into a single deployable unit backed by one primary database.

┌─────────────────────────────────────────────┐
│              Single Process / JVM / Runtime  │
│                                             │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
│  │  Orders  │  │  Users   │  │ Products │  │
│  │  Module  │  │  Module  │  │  Module  │  │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘  │
│       │              │              │        │
│  ┌────▼──────────────▼──────────────▼─────┐ │
│  │         Shared Data Access Layer        │ │
│  └────────────────────┬───────────────────┘ │
└───────────────────────┼─────────────────────┘
                        │
              ┌─────────▼─────────┐
              │   Single Database  │
              │   (PostgreSQL)     │
              └───────────────────┘

Pros and cons

Pros

  • Simple local development — run everything with one command.
  • In-process calls are orders of magnitude faster than network calls.
  • Single deployment pipeline reduces CI/CD complexity.
  • Easier to maintain ACID transactions across the entire domain.
  • Straightforward debugging with standard tools — no distributed tracing needed.
  • Refactoring module boundaries is cheap — rename a package, not a service contract.

Cons

  • Entire application restarts on every deployment, increasing change risk.
  • A bug in one module can crash the entire process.
  • Horizontal scaling scales all components together even when only one needs it.
  • Codebase can become a "big ball of mud" as team and feature count grow.
  • Technology lock-in — the entire codebase must use the same language and runtime.

Implementation notes

The key discipline for a healthy monolith is enforcing internal module boundaries. Treat each module as if it were a future service: define a public API for the module, prohibit direct cross-module database queries, and communicate between modules through well-defined interfaces or internal events. Tools like ArchUnit (Java), Dependency Cruiser (JavaScript/TypeScript), or NDepend (.NET) can enforce these boundaries in CI. A modular monolith — a monolith with rigorously enforced internal boundaries — is the optimal path for most teams and a natural precursor to selective service extraction via the strangler fig pattern.

Common failure modes

  • Big Ball of Mud: Modules freely call each other's internals, creating a tangled dependency graph that makes any change risky.
  • Shared mutable database: Multiple modules write to the same tables without going through a module API, creating hidden coupling.
  • Mega-constructor / god class: Business logic concentrates in a single class that becomes impossible to test or change.
  • Deployment bottleneck: As team grows, all teams wait on a single release train, killing deployment frequency.
  • Memory leaks in long-running processes: Without process isolation, a memory leak in one module degrades the entire application.

Decision checklist

  • Is the team fewer than 15 engineers who can coordinate releases easily?
  • Is the domain still being discovered — would service boundaries likely change in 6 months?
  • Does the team lack experience operating distributed systems (containers, service mesh, distributed tracing)?
  • Are the scaling requirements uniform across the application?
  • Can you enforce internal module boundaries via tooling and code review?
  • Is there a plan (strangler fig) for how to extract services if the monolith becomes a bottleneck?

Example use cases

  • Stack Overflow: One of the world's highest-traffic websites runs predominantly on a monolithic .NET application backed by SQL Server, demonstrating that monoliths scale further than often assumed.
  • Shopify: Began and operated for many years as a Rails monolith ("the Shopify monolith"), selectively extracting services only where independently needed.
  • Basecamp / Hey: Basecamp deliberately returned to a monolith after experimenting with SOA, citing productivity gains and operational simplicity.
  • Modular Monolith — The disciplined evolution of a monolith with enforced internal boundaries.
  • Microservices — The distributed alternative, appropriate once team size and domain complexity justify the operational cost.
  • Event-Driven Architecture — Can be adopted inside a monolith to decouple modules before extraction.

Further reading