Difficulty: Intermediate
What are coupling and cohesion? What is the ideal combination and how do you achieve it in code?
If SOLID is the set of rules for good object-oriented design, coupling and cohesion are the two dials you use to measure how well you have followed them. The one-line summary is: aim for low coupling and high cohesion.
Coupling is how much one module depends on the details of another. Cohesion is how strongly the responsibilities inside a single module belong together. Picture a kitchen. A well-organised kitchen has a cutting station with knives and boards together (high cohesion) and stations connected by simple hand-offs like a plate passing through a window (low coupling). A messy one has knives in the bathroom and every cook needing to know exactly how every other cook works.
High cohesion means a class does one focused job. A UserValidator that only validates users is cohesive. A class named Utils that has an email sender, a date parser, and a PDF generator is low cohesion; it changes for many unrelated reasons, is hard to name, and hard to reuse. Cohesion is closely tied to the Single Responsibility Principle.
Low coupling means a change in one class rarely forces changes in others. Tight coupling shows up when a class creates its collaborators using new, reaches into another object's internals, depends on concrete classes instead of interfaces, or relies on global mutable state. In the example, an OrderService that constructs MySqlOrderRepository directly can never be unit tested without a database, and switching database means editing the service. If instead it takes an OrderRepository interface through its constructor, the service does not know or care which storage is used, and tests can pass an in-memory version.
There are also degrees of coupling, from worst to best: content coupling (one module modifies another's internals), common coupling (shared global data), control coupling (passing flags that dictate behaviour), stamp coupling (passing whole objects when only a field is needed), and data coupling (passing only the required primitive values). Textbooks for university exams often list these.
How do you achieve the ideal? Depend on interfaces, not implementations (Dependency Inversion). Inject dependencies through constructors instead of creating them inside, which is what Spring and other frameworks automate. Follow the Law of Demeter, meaning a method should talk to its immediate friends only, and avoid chains like a.getB().getC().doIt(). Keep classes small and focused. Use events or the Observer pattern when the sender should not know its receivers.
Be balanced when answering. Zero coupling is impossible and undesirable, since components must interact, and extreme decoupling through layers of interfaces creates needless complexity. The goal is to place loose couplings at the boundaries that are actually likely to change.
Interviewers like a concrete metric or heuristic: if a change request touches many classes, coupling is too high; if you struggle to name a class without using "and" or "manager", its cohesion is probably low.
interface OrderRepository { void save(String order); }
class MySqlOrderRepository implements OrderRepository {
public void save(String o) { System.out.println("MySQL saved: " + o); }
}
class InMemoryOrderRepository implements OrderRepository {
public void save(String o) { System.out.println("Memory saved: " + o); }
}
class OrderService {
private final OrderRepository repo; // depends on abstraction
OrderService(OrderRepository repo) { this.repo = repo; }
void place(String order) { repo.save(order); }
}
public class CouplingDemo {
public static void main(String[] args) {
new OrderService(new MySqlOrderRepository()).place("Order-1");
new OrderService(new InMemoryOrderRepository()).place("Order-2");
}
}
OrderService never mentions a concrete repository, so storage can change or be mocked without touching it.
coupling, cohesion, dependency-injection, design-quality