System DesignBeginner 12 min Lesson 29 of 42

Monolith

One deployable application containing everything. Its genuine advantages, its real limits, and why most projects should start here.

System Design · Lesson 29 of 42
0/42 done(0%)

What is it? #

A monolith is one application, deployed as one unit, containing all the features of a system.

It has a poor reputation that it mostly does not deserve. The problems people attribute to monoliths — tangled code, slow builds, fearful deployments — come from bad structure, not from being one deployable.

The advantages are substantial. One codebase to search, one deployment, no network calls between components, real transactions across your data, and straightforward local development.

The genuine limits arrive with scale of team rather than scale of traffic: many people changing one codebase, one slow test suite gating everyone, and one technology choice for everything.

Think of it like this #

A house where the kitchen, bedrooms and living room are all under one roof. Moving between them is instant, there is one set of plumbing, and one front door to lock.

Building separate buildings for each room gives independence, but now you need paths, separate utilities and an umbrella to get to dinner.

Simple example #

An e-commerce application with products, orders, payments, users and admin, all in one Django or Rails project, deployed as one service behind a load balancer.

Code #

TEXT
shop/
├── users/            authentication, profiles
├── catalogue/        products, categories, search
├── orders/           cart, checkout, order history
├── payments/         gateway integration, refunds
├── notifications/    email and SMS
├── admin/            internal tools
└── main.py           one application, one deployment

One database. One deploy. One codebase. Function calls between modules.
PYTHON
# The strongest practical advantage: one transaction across everything
def place_order(user, cart) -> Order:
    with db.transaction():                       # all of this, or none of it
        order = orders.create(user=user, lines=cart.lines)
        inventory.reserve(cart.lines)
        payment = payments.charge(user, order.total)
        order.mark_paid(payment.id)
        return order
    # If the charge fails, the reservation and the order are rolled back
    # automatically. In a distributed system this becomes a saga with
    # compensating actions — considerably more work to get right.
TEXT
Honest comparison

Monolith wins at            Monolith struggles with
------------------------    ----------------------------------
transactions                many teams editing one codebase
local development           one slow test suite blocking everyone
refactoring across code     scaling one hot component independently
debugging one process       one language and runtime for everything
operational simplicity      a bad deploy affecting the whole system
lower infrastructure cost   very large codebases becoming slow to build

How it works #

Modules call each other as functions. There is no serialisation, no network hop, no partial failure between components, and no version skew between them.

The transaction example is the advantage that is hardest to replicate elsewhere. Creating an order, reserving stock and taking payment in one atomic operation is a few lines here. Split across services, it becomes a saga with compensating transactions and a great deal of careful failure handling.

Deployment is one artefact. There is no orchestration of which service version works with which, and rollback is one action.

Debugging is one process with one stack trace and one log stream. Reproducing a bug locally means running one application.

The limits are worth stating plainly. A build and test cycle that takes forty minutes slows every developer. Fifty people in one codebase produces constant merge friction. And scaling the whole application because one endpoint is heavy is wasteful.

Note that scaling the monolith horizontally still works — you run several copies behind a load balancer. What you cannot do is scale one part of it independently.

Real-world use #

Most successful products are monoliths for years, and many remain so at considerable scale. Splitting early, before the boundaries are understood, produces distributed systems with all the complexity and none of the benefit.

The practical advice from teams that have done both is consistent: start with a monolith, keep it well structured internally, and extract services only when a specific, felt problem justifies it.

Specific problems that justify extraction include a component with genuinely different scaling needs, a team that needs to deploy independently, or a workload requiring a different runtime.

The modular monolith, covered next, is the middle path that keeps the operational simplicity while addressing internal structure.

When a monolith does become painful, the pain is usually measurable: build time, deployment frequency, merge conflicts and the time it takes a new developer to become productive.

Common mistakes #

  • Blaming the monolith for problems caused by poor internal structure.
  • Splitting into services before the boundaries are understood.
  • Letting the test suite grow so slow that people stop running it.
  • Sharing a single database schema with no internal module boundaries.
  • Assuming a monolith cannot scale — it scales horizontally like any stateless service.

Practice #

Take a system you know and list its major functional areas. For each, note how often it changes, who changes it, and whether it has different scaling needs. Then identify which one, if any, has a genuine case for being extracted first.

Quick quiz

  1. 1. What defines a monolith?

  2. 2. What is the strongest technical advantage of a monolith?

  3. 3. What kind of scale usually causes monolith pain first?

  4. 4. Can a monolith scale horizontally?

  5. 5. When should you extract a service?

Summary

  • A monolith is one deployable containing all features.
  • It offers real transactions, simple debugging and low operational cost.
  • Its limits appear with team and codebase size, not traffic.
  • It still scales horizontally; it just cannot scale parts independently.
  • Start here and extract services only for a concrete reason.