AlgoMaster Logo

Monolithic Architecture

Medium Priority14 min readUpdated October 8, 2026
AI Mock Interview

Practice this topic in a realistic system design interview

Listen to this chapter
Unlock Audio

Premium Video

This video is available to premium subscribers only

Unlock Full Access

Starting a new system as one deployable application is usually the simplest choice. You have one codebase, one build, one deployment, and one place to debug. The real question is how long that simplicity lasts as the system and the team grow.

This chapter covers what a monolithic architecture is, why so many applications start as monoliths, the problems that show up as they grow, and how to decide whether a monolith is the right choice for your system.

1. Where Most Applications Begin

Suppose you're building a new e-commerce website. You need user accounts, a product catalog, a shopping cart, orders, and payments.

The simplest approach is to build everything as one application: one codebase, one build, one deployment, with a single database behind it.

When a user signs up, the user module handles it. When they place an order, the order module handles it. But all of these features live inside the same application and run in the same process.

This is called a monolithic architecture. And it's how many applications start, including some that eventually grow to serve millions of users.

2. What Is a Monolithic Architecture?

Inside the codebase, the application can still be organized into modules. There might be a users module, a catalog module, an orders module, and a payments module.

But these modules are not separate programs:

  • Packaged together: They are compiled or packaged into one artifact.
  • One process: They run inside the same process and call each other with normal function calls.
  • One database: They usually share the same database.

So if you change one line in the payments module, you rebuild and redeploy the entire application. That's the defining trait of a monolith.

It's not about how messy or clean the code is. It's about the application being deployed as one piece. A well-designed monolith can have clear modules and clear ownership. A poorly designed one is just tangled code in one process.

3. How a Request Flows Through a Monolith

Let's follow a request through our e-commerce monolith.

All function calls inside one processPlace OrdercheckStock(items)in stockcharge(card, total)chargedBEGIN; insert order; reduce stock; COMMITOrder confirmedUserOrder HandlerInventory ModulePayments ModuleDatabaseUserOrder HandlerInventory ModulePayments ModuleDatabase
8 / 8
algomaster.io
  1. A user clicks Place Order. The request reaches the application's order handler.
  2. The order code calls the inventory module to check that the items are in stock.
  3. It calls the payments module to charge the user's card.
  4. Then it saves the order to the database.

Every one of those steps is just a function call inside the same process. There's no network hop between modules.

And because everything shares one database, the order can be saved and the inventory reduced inside a single database transaction:

If saving the order fails, the stock change is rolled back too. In a distributed system, the same workflow would need patterns like sagas and compensating actions. This simplicity is a big part of why monoliths are so common.

4. The Layered Structure

Many monolithic applications organize their code into layers.

  • Presentation layer: Handles incoming requests and sends responses, through things like HTTP controllers or web pages.
  • Business logic layer: Contains the rules of the application, such as how discounts are calculated or when an order can be cancelled.
  • Data access layer: Reads from and writes to the database.

A typical request flows down through these layers, and the response flows back up.

Larger applications often split the business logic layer in two: an application layer that coordinates use cases, and a domain layer that holds the business rules. The data access layer is then often called the infrastructure layer, since it also talks to queues and external APIs.

LayerResponsibilityExamples
PresentationAccept requests and return responsesControllers, REST endpoints, views
ApplicationCoordinate use cases and workflowsCheckout flow, refund workflow
DomainApply business rulesDiscount rules, "cancelled orders can't ship"
InfrastructureTalk to databases and external systemsRepositories, queue clients, payment provider clients

This structure keeps different responsibilities separated, but the separation is based on technical roles rather than business features. For example, order logic and payment logic may both live in the same business layer, even though they belong to different parts of the application. We'll come back to this in section 13.

5. Why Monoliths Work Well

So what makes a monolith a good choice?

  • Simple development: There's one repository to clone, one application to run locally, and one place to search when you're debugging.
  • Simple deployment: You build one artifact, such as a JAR file or a container image, and deploy it. There's no need to coordinate releases across a dozen services.
  • Easier testing: An end-to-end test can start the whole application and exercise a complete user flow in one place.
  • Performance: Calls between modules are in-memory function calls, which are much faster than network calls between services.
  • Data consistency: With one database, you get normal transactions, joins, and foreign keys without any distributed coordination.

There's also a benefit that's easy to overlook: refactoring. Renaming a method or moving a class across modules is one commit, checked by the compiler and the test suite. Across services, the same change needs staged deployments and temporary compatibility code.

For a small team building a new product, these benefits usually outweigh the drawbacks.

6. Scaling a Monolith

As traffic grows, a monolith can scale in the same ways as most applications.

  1. Vertical scaling: We move the application to a bigger machine with more CPU and memory.
  2. Horizontal scaling: We run several identical copies of the monolith and put a load balancer in front of them. Each copy contains the full application, and any copy can handle any request.

For this to work, the application should be stateless. Sessions and shared data should live in a database or a cache, not in one server's memory.

On the data side, we can add read replicas, caching, and indexing. Slow work like sending emails or generating reports can move to background workers, so it doesn't hold up user requests.

With these techniques, a well-built monolith can serve a very large number of users. But as the application and the team keep growing, a different set of problems starts to appear.

7. The Codebase Keeps Growing

Suppose our e-commerce site succeeds. A few years later, the codebase has grown from ten thousand lines to over a million. The team has grown from five engineers to eighty.

Now simple things start to slow down:

  • Building the application takes longer.
  • Running the full test suite might take thirty minutes or more.
  • A new engineer needs weeks to understand how the pieces fit together.

None of this breaks the application. But it steadily reduces how fast the team can ship changes. When every pull request waits a long time for feedback, developers start batching changes together, which makes each change riskier.

8. Tight Coupling

The bigger problem is coupling. In a monolith, there is often nothing stopping one part of the application from reaching directly into another.

Suppose the marketing team needs order data for a report. The quickest solution might be to query the orders table directly or call an internal method from the orders module. It works, and the feature ships quickly. But now the marketing code depends on the internal details of the orders system.

Repeat this across hundreds of features over several years, and those dependencies start to pile up. Soon, changing something as simple as the structure of the orders table might unexpectedly break reporting, shipping, or invoicing.

When a codebase becomes tangled like this, it is often called a big ball of mud. Even small changes start to feel risky because it becomes difficult to know what else might be affected.

Coupling is not an unavoidable result of having one codebase. It comes from many small shortcuts that nobody stopped. The modular monolith in section 13 is the usual fix.

9. Deployment Becomes Risky

Because the monolith is one deployable unit, every change ships together.

Suppose the search team fixes a small bug. To release it, the whole application has to be rebuilt, tested, and redeployed. That release also contains whatever the payments team, the orders team, and the catalog team merged that week. If any one of those changes has a problem, the entire release may need to be rolled back.

So teams start coordinating release schedules. Releases become less frequent, and each one carries more changes. And larger releases are riskier, because more things can go wrong at once.

Feature flags, canary releases, and fast rollbacks reduce the risk, but they don't change the fact that the application ships as one unit. This is often one of the first pain points that pushes large organizations to consider splitting a monolith.

10. All-or-Nothing Scaling

Scaling can also become inefficient as a monolith grows.

Suppose we add a feature that resizes every product image uploaded by sellers. Image processing is CPU-intensive, while browsing the product catalog is mostly lightweight read traffic.

If both are part of the same monolith, we cannot easily scale just the image-processing workload. Instead, we have to run more copies of the entire application, including the catalog, cart, checkout, and everything else. Each copy also needs enough memory and resources to run the full application, even though only one part is under heavy load.

As a result, we end up paying for capacity that most of the system does not need. With separate services, we could scale the image-processing service independently and leave the rest of the system as it is.

A middle ground also exists. The monolith can push image jobs onto a queue and run a separate pool of worker processes from the same codebase, so only the workers scale up.

11. Weak Fault Isolation

In a monolith, all modules share the same process. That means a problem in one module can affect all the others.

Suppose the recommendation module has a memory leak. Over a few hours, it slowly consumes all the available memory. Eventually, the process crashes. And when it crashes, it doesn't just take down recommendations. It also takes down login, checkout, and payments, because they were running in the same process.

Other shared resources fail the same way. A slow query can fill the shared database connection pool, and a slow external API can tie up all the request threads.

Good practices like timeouts and resource limits help, but the modules are never fully isolated from each other.

12. Technology Lock-In

A monolith is usually built with a single language and framework. Early on, this is often an advantage because everyone on the team works with the same tools and follows the same conventions.

But as the system grows, that consistency can start to become a limitation. Suppose you want to add a machine learning feature, and the best libraries for it are in Python, while the rest of your application is written in Java. Introducing another language into the same monolith can be awkward and difficult to maintain.

Framework upgrades can also become painful. Moving a million-line application to a new major version may take months because many parts of the system have to be tested and updated together.

The answer is not always a full rewrite. Often the better choice is to keep the core monolith and run only the specialized workload, like the ML feature, as a separate service.

13. The Modular Monolith

So, does this mean every monolith eventually turns into a mess? Not necessarily.

A common middle ground is the modular monolith. It is still a single application with a single deployment, but the code is organized around business domains such as users, catalog, orders, and payments.

Each module exposes a small public interface, and other modules are expected to use that interface instead of reaching into its internal classes.

Modules can also own their own database tables. So if the shipping module needs order data, it asks the orders module for it instead of querying the orders table directly.

The folder structure usually follows business areas first, with layers inside each module:

Teams can enforce these boundaries with build rules or automated architecture tests, so violations are caught early. For example, a test can fail the build if anything outside orders/ imports from orders/internal/. Tools like ArchUnit for Java and Packwerk for Ruby are built for this.

This gives you much of the simplicity of a monolith without allowing the codebase to become tightly coupled. And if one module eventually needs to become its own service, the clean boundary makes that transition much easier.

14. Monolith vs Microservices

The usual alternative to a monolith is a microservices architecture. In microservices, each business capability runs as its own service, with its own deployment and usually its own database.

This solves several monolith problems. Teams can deploy independently. Services can be scaled separately. And a failure in one service is less likely to take down the rest.

But microservices bring their own costs. Calls between services go over the network, so they are slower and can fail. Transactions that span several services need patterns like sagas. And you need service discovery, distributed tracing, and a lot more infrastructure to operate the system.

MonolithModular monolithMicroservices
DeploymentOne unitOne unitMany independent units
Calls between partsIn-processIn-process, through module APIsOver the network
TransactionsNormal database transactionsNormal database transactionsSagas, eventual consistency
ScalingWhole appWhole app (or worker pools)Per service
Fault isolationWeakWeakStronger
Operational costLowLowHigh
Main riskTangled codeBoundaries need enforcingDistributed-system complexity

So it's not that one is better than the other. A monolith trades independent scaling and deployment for simplicity. Microservices trade simplicity for independence.

Plenty of successful companies run large monoliths today. Shopify, for example, runs one of the largest Ruby on Rails monoliths, organized as a modular monolith.

15. Moving Away from a Monolith

Suppose you decide that parts of your monolith do need to become separate services.

Rewriting everything from scratch is risky. It can take years, and the old system keeps changing while you rewrite it.

A safer approach is the strangler fig pattern. You pick one piece of functionality, such as notifications, and build it as a new service. Then you route notification requests to the new service instead of the monolith. Once it's stable, you remove the old code from the monolith. Then you repeat with the next piece.

Over time, the new services gradually replace parts of the monolith, while the application keeps running the whole time.

Many teams also find that they only need to extract a few heavily loaded parts, and the rest of the monolith can stay as it is.

Extraction goes much more smoothly if the module already has a clean boundary and owns its own tables. If every module reads every table, splitting one out becomes a long data migration project. That's another reason to modularize first.

16. When Should You Use a Monolith?

A monolith is often a strong choice when you are starting a new product. Splitting the application into services too early can lock you into the wrong boundaries, and those boundaries can be expensive to change later.

A monolith also works well when the team is small, the domain is relatively simple, or strong data consistency is important.

Start with a monolith when...Consider splitting when...
You are building a new productMultiple teams are constantly blocked by shared releases
The team is smallOne part needs to scale very differently from the rest
The domain is still changingFailures in one area frequently affect the entire system
Strong data consistency mattersOne workload needs a different language or runtime
You have limited capacity to run infrastructureModule boundaries are already clear and stable

You should start thinking about splitting the system only when real problems begin to appear. For example, when multiple teams are constantly blocked by shared releases, when one part of the application needs to scale very differently from the rest, or when failures in one area frequently affect the entire system.

Even then, you do not necessarily need to move everything to microservices. A modular monolith, or extracting only a few parts into separate services, is often enough.

Summary

  • A monolith is an application where all features are built, deployed, and run as a single unit. It's defined by how it's deployed, not by how clean the code is.
  • Requests flow through in-process function calls, and one shared database gives you normal transactions.
  • Many monoliths use layers: presentation, business logic (often split into application and domain), and data access.
  • The main benefits are simple development, simple deployment, easier testing, fast in-process calls, and strong data consistency.
  • A stateless monolith can scale vertically and horizontally behind a load balancer, with caches and read replicas.
  • As it grows, problems appear: slow builds, tight coupling (a big ball of mud), risky shared releases, all-or-nothing scaling, weak fault isolation, and technology lock-in.
  • A modular monolith keeps one deployment but organizes code by business domain, with public interfaces and owned tables enforced by tooling.
  • Microservices trade simplicity for independence. Neither is better in general.
  • To move away safely, use the strangler fig pattern and extract one piece at a time.
  • Start with a monolith, keep it modular, and split only when real problems appear.

Quiz

Monolithic Architecture Quiz

10 quizzes