Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 2

Coupling and Cohesion Issues in Monoliths

Recommended resources

Welcome back! In our previous lesson, we established a clear picture of monolithic architecture, particularly its layered structure, using the restaurant kitchen as our guide. We saw how organizing a single codebase into Presentation, Business, and Data Access layers brings order to development.

However, as a monolith grows, this neat structure can start to fray. Unseen forces can tangle the codebase, making it fragile and difficult to change. This lesson introduces two critical software design principles, cohesion and coupling, that allow us to name and measure these forces. Our goal is for you to be able to look at a monolithic codebase and pinpoint exactly where these problems lie. Understanding this is the first step toward appreciating why architectures like microservices were created.

High Cohesion, Low Coupling: The Ideal

Before diving into code, let's establish our core principles. In software design, we strive for:

  • High Cohesion: Elements within a module (like a class or a function) should be strongly related and focused on a single, well-defined task.
  • Low Coupling: Different modules should be as independent as possible, with minimal knowledge of each other's internal workings.

The following images provide excellent analogies for these concepts.

Cohesion vs. Coupling Visual Analogy
This image contrasts high and low cohesion using a kitchen analogy (specialized chefs vs. one overwhelmed chef) and loose vs. tight coupling using a highway analogy (smooth traffic flow vs. a bottleneck).
Bad vs. Good Modularization: Cohesion and Coupling
This diagram illustrates the concepts more abstractly. "Bad modularization" shows low cohesion and high coupling, with tangled dependencies between modules. "Good modularization" shows the ideal: components within each module are tightly related (high cohesion), and connections between modules are minimal (low coupling).

To get a more formal definition of these terms and understand their importance, let's turn to a short video.

Cohesion and Coupling: Write BETTER PYTHON CODE Part 1

Watch this first part of the video "Cohesion and Coupling: Write BETTER PYTHON CODE Part 1" by ArjanCodes.

Focus on the definitions and examples provided. Pay close attention to the distinction between a function with weak cohesion (doing many unrelated things) and one with strong cohesion (doing one thing). Also, note how the check_email_security function demonstrates high coupling by reaching deep into the email object's structure.


The Problem of Low Cohesion

A module or function with low cohesion is a classic "Jack of all trades, master of none." It tries to do too many unrelated things at once. This leads to several problems that you've likely encountered in large codebases.

Understanding Cohesion, Coupling and Connascence in Software Design

This article clearly outlines the negative consequences of poor cohesion.

Read the sections titled "What is Low Cohesion?" and "Drawbacks of Low Cohesion." As you read, think about a time you've had to modify a function that did too many things and how it made the task more difficult and risky.

As the article states, low cohesion makes code harder to read, harder to maintain, and less reusable. A change to one of its many responsibilities can have unintended side effects on the others.


The Problem of High Coupling

High coupling is when two or more modules are tightly entangled. A change in one module often requires a corresponding change in the other. This creates a fragile system where a small modification can cause a cascade of breakages across the application.

Let's look at a concrete example of both problems in a monolithic context. Imagine an e-commerce application's "place order" functionality.

Splitting up a Monolith to (micro)Services

Watch this segment from "Splitting up a Monolith to (micro)Services" by CodeOpinion.

Observe how the PlaceOrder controller is structured. Notice how it directly depends on a ShippingService and a PaymentService, and even shares a database transaction with them. This is a classic example of high coupling in a monolith. The controller itself also exhibits low cohesion by orchestrating database writes, shipping label creation, and payment processing all in one place.

This tight coupling through direct, in-process calls and a shared database transaction means the Sales, Shipping, and Billing parts of the application are deeply intertwined. You can't change how shipping labels are created without potentially impacting the order placement process.


Identifying and Fixing the Problems in Code

Now, let's get practical. We'll analyze a Python codebase that suffers from low cohesion and high coupling, and then see how to refactor it.

The following video uses an example of a VehicleRegistry application. First, let's look at the initial, problematic code.

Cohesion and Coupling: Write BETTER PYTHON CODE Part 1

Watch this section from the ArjanCodes video, which introduces the sample application.

Watch from the introduction of the code to the end of the problem description. Pay close attention to the register_vehicle method in the Application class.

Your Turn: Code Analysis

Before we see the solution, take a moment to analyze the register_vehicle method yourself. Based on what you've learned:

  1. Cohesion: Can you list the different, unrelated responsibilities that this single method is handling? Why does this demonstrate low cohesion?
  2. Coupling: How does this method's interaction with the VehicleRegistry class demonstrate high coupling? What would happen if the VehicleRegistry's implementation changed?

Think through your answers before proceeding.


The Refactoring Process

Now, let's see how an expert developer would refactor this code to improve its structure. The core idea is to move code closer to the data it operates on.

Cohesion and Coupling: Write BETTER PYTHON CODE Part 1

Watch the refactoring part of the ArjanCodes video.

Follow along as the code is refactored from start to finish. Focus on these key changes: The creation of Vehicle and VehicleInfo classes. How does this increase cohesion? Moving the responsibility for creating a vehicle into the VehicleRegistry class. How does this reduce coupling? Moving the tax calculation logic into the VehicleInfo class, which is its "information expert."

After the refactoring, each class and method has a single, clear responsibility (high cohesion). The Application class no longer needs to know the internal details of how a vehicle ID or license plate is generated; it just asks the VehicleRegistry to create a vehicle (low coupling).

As the video summarizes, this new structure is much easier to maintain and extend.

Cohesion and Coupling: Write BETTER PYTHON CODE Part 1

Watch the final summary of the refactored code's benefits.

This short clip from recap and summary demonstrates how easy it is to add a new vehicle brand or change the tax rules in the new design, reinforcing the practical value of high cohesion and low coupling.


A Deeper Example: Coupling and Testability

Let's look at one more powerful example that connects high coupling directly to a pain point you've likely experienced as a developer: difficult testing.

On Coupling and Abstractions

This article walks through a file synchronization utility, showing how an initial design that mixes logic with I/O is hard to test, and then refactoring it into a clean, testable design.

Please read through this example, paying attention to the following stages: The Initial Problem: Start with the introduction that defines coupling and cohesion in the context of large systems. The Tightly Coupled Code: Examine the first implementation of the sync function and the corresponding tests. Notice how the tests are complex because they have to create real files and directories. This is because the core logic is coupled to the filesystem I/O. Choosing Abstractions: Understand the key insight: separating what to do from how to do it. The refactoring aims to have the core logic work with simple data structures (dictionaries of hashes, lists of commands) instead of the real filesystem. The Refactored Code: Look at the final implementation, which is split into three parts: read_paths_and_hashes (gathering input), determine_actions (the testable "functional core"), and the top-level sync function (the "imperative shell"). Notice how clean the tests for determine_actions become.

This sync example is a masterclass in reducing coupling. The final determine_actions function is highly cohesive (it does one thing: decides which actions to take) and is completely decoupled from the filesystem. This is the kind of clean, testable design that is easy to maintain in a large monolithic application but is often lost as features are hastily added, leading to the "big ball of mud."


Conclusion

In this lesson, we've moved from the "what" of monolithic architecture to the "how it goes wrong." You've learned to identify the key symptoms of architectural decay: low cohesion and high coupling.

Key Takeaways:

  • High Cohesion is when the components of a module belong together. A function should do one thing and do it well.
  • Low Coupling is when modules are independent. Changes in one module shouldn't force changes in another.
  • Low cohesion leads to code that is hard to understand, maintain, and reuse (e.g., the initial register_vehicle method).
  • High coupling leads to fragile code that is difficult to test and change (e.g., the PlaceOrder controller and the initial sync function).
  • Refactoring to improve cohesion and coupling often involves creating more focused classes and functions, and introducing abstractions to hide implementation details.

These code-level issues are microcosms of larger architectural problems. When an entire monolithic application is riddled with high coupling, it becomes incredibly difficult to scale. In our next lesson, we will explore these scaling limitations, examining how both vertical and horizontal scaling strategies begin to fail in a tightly coupled monolith.