Software Design

Coupling and Cohesion: The Roots of Software Design

The code clearly runs, but why does changing one feature require edits in so many places?

4 min read
Cover image for Coupling and Cohesion: The Roots of Software Design

The code clearly runs, but why does changing one feature require edits in so many places?

You change one button color, and suddenly the payment logic breaks. We have all been there.

Coupling and cohesion are the two concepts that explain this in a nutshell.

Lower coupling is better, while higher cohesion is better. Much of good design starts with this sentence.

This article explains what these concepts really mean, why they are called the roots of software design principles, and how to apply them to real code, based on my experience.

Let’s start with the key points.

  1. Coupling: how tightly modules are intertwined → the lower, the better
  2. Cohesion: how much the code in one module focuses on a single responsibility → the higher, the better
  3. These two concepts underpin famous principles such as SOLID and design patterns.
  4. The goal is simple: code that is easy to change and replace.

Coupling and Cohesion: What Are They, Exactly?

In one sentence, the two concepts can be summarized like this.

Coupling describes relationships between modules, while cohesion describes the internal unity of a module.

Because they point in opposite directions, they can be confusing. I remembered them this way.

Coupling is “distance from the outside”; cohesion is “how tightly things group together inside.”

When coupling is high, fixing A drags B and C along with it—like dominoes.

When cohesion is low, payment logic, email delivery, and logging are mixed into one class. You cannot tell what the class does just from its name.

Good design pushes these in opposite directions: loosely connected outside, tightly focused inside.


Why Are These the “Roots” of Design Principles?

Coupling and cohesion were first formalized in structural design theory in the 1970s. They are commonly attributed to Larry Constantine.

They still hold up after nearly half a century because they express fundamentals independent of any particular language or trend.

When you unpack the well-known principles we use, they ultimately converge here.

Principle · Pattern What it ultimately says
Single Responsibility Principle (SRP) Increase cohesion
Dependency Inversion Principle (DIP) Reduce coupling
Interface Segregation Principle (ISP) Break unnecessary coupling
Most design patterns Low coupling + high cohesion

See? The names differ, but they share one root.

When I learn a new principle or pattern, I first ask: “Is this meant to reduce coupling or increase cohesion?” That gets me halfway there.

I map out the structure by drawing boxes and arrows on paper like this.
I map out the structure by drawing boxes and arrows on paper like this.

How Do You Reduce Coupling? Let’s Look at Code.

Let’s look at a short example, since explanations alone can feel abstract.

The code below has high coupling. The order class knows a specific payment provider directly.

class Order {
    let pay = KakaoPay()  // Tightly coupled to Kakao Pay
    func checkout() {
        pay.send()        // This must be torn apart to switch to another payment method
    }
}

The moment you switch the provider to Toss, you have to open and modify Order. That is a textbook example of high coupling.

Now let’s create some distance with a protocol.

class Order {
    let pay: Payment                // 'Depends only on the “payment” contract
    init(pay: Payment) { self.pay = pay }
    func checkout() { pay.send() }  // It does not matter what comes in
}

Now Order does not care which payment provider arrives. You only need to swap the implementation.

Adding one interface makes the coupling this loose.
Adding one interface makes the coupling this loose.

After making this change in production, I barely had to touch the existing code when requests came in to add payment providers. That is the power of low coupling.


How Do You Increase Cohesion?

Cohesion is judged by whether a module does exactly one thing.

My rule is simple: if naming a class naturally brings all its methods to mind, it has high cohesion.

For example, finding these mixed together in UserService is a warning sign.

  • Sign-up processing (the core responsibility)
  • Marketing email delivery (someone else’s job)
  • CSV report generation (also someone else’s job)

It is like unrelated tenants renting rooms in the same house.

In that case, give email its own room as EmailSender and reports their own as ReportGenerator.

Then you no longer need to open UserService to change the email logic later. The ripple effect of changes becomes smaller.

Low coupling and high cohesion ultimately aim in the same direction: preventing changes from spreading.


Frequently Asked Questions (Q&A)

Q. Which should I focus on first, coupling or cohesion?

I recommend starting with cohesion. As you split modules so each focuses on one responsibility, coupling often gets cleaned up naturally.

Q. Is zero coupling always best?

No. If modules are not connected at all, the program cannot run. The goal is not zero, but “only as much as necessary, and loosely.”

Q. Do I need to design everything perfectly from the start?

Not at all. I first make things work, then gradually loosen coupling and gather cohesion whenever changes start to hurt. Refactoring is inherently iterative.


Coupling and cohesion are not flashy new technologies, but they are common habits among people who write code that lasts.

In the code you wrote today, ask yourself once: “How far will this change reach?” As that question accumulates, your design instincts grow quickly. You’ve got this!