Back to Coupling & Design
Q2Question 2 of 2

What is tight coupling vs loose coupling?

Key Concepts
  • Tight coupling: class depends directly on a concrete implementation (new SmsService() inside TradeService)
  • Loose coupling: class depends on an interface and receives the concrete implementation through dependency injection
  • Tight coupling prevents swapping implementations, makes testing difficult, and propagates changes across classes
  • Loose coupling enables: swapping implementations at runtime, unit testing with mocks, and independent changeability
  • Four signs of tight coupling: new inside class, concrete class in signatures, static utility calls, direct field access
  • In Spring, every @Service with constructor injection of interfaces is loose coupling in action

Definition

Tight coupling means depending on a concrete class (new X() inside). Loose coupling means depending on an interface plus injection (DI). Loose enables swapping, testing with mocks, and changing without breaking. OOP plus DI always strives for loose coupling.

Two levels of dependency between classes.

  • Tight coupling: class A directly creates and depends on a concrete class B with new SmsService(). If SmsService changes, TradeService breaks. Testing without real SMS is impossible.
  • Loose coupling: class A depends on an interface, and the concrete implementation is injected from outside (Dependency Injection). TradeService depends on NotificationService (interface). Spring injects SmsService, EmailService, or MockService.
  • Three benefits of loose coupling: swappability (change implementations without modifying consumers), testability (inject mocks for fast isolated tests), maintainability (implementation changes do not propagate).
  • Four signs of tight coupling: new inside the class, concrete class in method signatures, static method calls to concrete utilities, direct field access on another class.

Tight coupling: the bank teller at National Bank of India has a personal phone (SmsService) glued to the desk. Can only send SMS. Need email? Rip out the phone and rewire the desk. Loose coupling: the teller has a communication slot (NotificationService interface). Plug in a phone (SMS), plug in a computer (email), plug in a fax (legacy). Swap devices without touching the desk. Test with a dummy device (mock).

Understanding the Concepts

What is tight coupling vs loose coupling?

Coupling measures how strongly one class depends on another.

What does tight coupling look like?

JAVA
public class TradeService {
    private final SmsService smsService = new SmsService();          // hardwired
    private final PostgresTradeRepository tradeRepo = new PostgresTradeRepository(); // hardwired

    public void executeTrade(Trade trade) {
        tradeRepo.save(trade);                                       // locked to PostgreSQL
        smsService.send(trade.getAccountId(), "Trade executed");     // locked to SMS
    }
}
// Cannot switch to EmailService without modifying TradeService.
// Cannot test without real PostgreSQL database.
// Cannot test without sending real SMS.

What does loose coupling look like?

JAVA
public class TradeService {
    private final NotificationService notifier;   // interface
    private final TradeRepository tradeRepo;       // interface

    public TradeService(NotificationService notifier, TradeRepository tradeRepo) {
        this.notifier = notifier;
        this.tradeRepo = tradeRepo;
    }

    public void executeTrade(Trade trade) {
        tradeRepo.save(trade);                     // could be Postgres, MongoDB, InMemory
        notifier.send(trade.getAccountId(), "Trade executed"); // could be SMS, Email, Mock
    }
}
// Swap SMS to Email: inject EmailService. Zero changes to TradeService.
// Test: inject MockNotificationService + InMemoryTradeRepository. No real DB or SMS.

What are the four signs of tight coupling?

JAVA
// Sign 1: new inside the class
private final SmsService sms = new SmsService(); // tight
private final NotificationService notifier;       // loose (injected)

// Sign 2: concrete class in method signature
public void process(PostgresTradeRepository repo) { } // tight
public void process(TradeRepository repo) { }         // loose (interface)

// Sign 3: static method calls to concrete utilities
String hash = CryptoUtils.sha256(data);  // tight
String hash = hashService.hash(data);    // loose (inject interface)

// Sign 4: direct field access on another class
account.balance = amount;    // tight (breaks encapsulation)
account.deposit(amount);     // loose (uses public method)

What are the three benefits of loose coupling?

JAVA
// Benefit 1: Swappable at runtime
// Production: new TradeService(new SmsService(), new PostgresRepo())
// A/B test:   new TradeService(new EmailService(), new PostgresRepo())
// Migration:  new TradeService(new SmsService(), new MongoRepo())

// Benefit 2: Testable with mocks
@org.junit.jupiter.api.Test
void executeTrade_sendsNotification() {
    NotificationService mock = org.mockito.Mockito.mock(NotificationService.class);
    TradeRepository mockRepo = org.mockito.Mockito.mock(TradeRepository.class);
    TradeService service = new TradeService(mock, mockRepo);
    service.executeTrade(new Trade("TRD-001", "RELIANCE", amount));
    org.mockito.Mockito.verify(mock).send("SAV2001", "Trade executed");
    // Test runs in 10ms. No database. No SMS gateway.
}

// Benefit 3: Maintainable
// SmsService switches from Twilio to AWS SNS: zero changes to TradeService.

How do tight and loose coupling compare?

| Aspect | Tight Coupling | Loose Coupling |
|--------|---------------|----------------|
| Dependency | Concrete class | Interface |
| Creation | new inside class | Injected from outside |
| Swappability | Requires code change | Zero code change |
| Testability | Needs real dependencies | Inject mocks |
| Change propagation | Breaks consumers | Isolated |

What should a senior engineer know?

  • In Spring, every @Service with constructor injection of interfaces is loose coupling in action.
  • The Dependency Inversion Principle (D in SOLID) formalizes loose coupling: depend on abstractions, not on concrete classes.
  • Loose coupling enables microservice architecture: services communicate through interfaces (REST APIs, message queues), not through direct class dependencies.

Visual Overview

DIAGRAM
  TIGHT COUPLING
  ┌────────────────────────┐
  │ TradeService            │
  │ new SmsService()  ←──── │── hardwired
  │ new PostgresRepo() ←─── │── hardwired
  └────────────────────────┘
  Cannot swap. Cannot mock. Cannot test fast.

  LOOSE COUPLING
  ┌────────────────────────┐
  │ TradeService            │
  │ NotificationService ←── │── interface
  │ TradeRepository     ←── │── interface
  └────────────────────────┘
         ↑ injected
  ┌──────┴───────────────────┐
  │ SmsService (production)   │
  │ MockService (testing)     │
  │ EmailService (A/B test)   │
  └──────────────────────────┘

Key Takeaway

Tight coupling means a class directly depends on concrete implementations, creating them with new internally. This locks the class to specific dependencies: cannot swap SMS for email, cannot test without a real database, cannot change the notification provider without modifying the service. Loose coupling means a class depends on interfaces and receives concrete implementations through Dependency Injection. The class knows what it needs (NotificationService) but not how it is implemented (SMS, email, push, mock).

OOP plus DI strives for loose coupling because it enables swappability (change implementations without modifying consumers), testability (inject mocks for fast isolated tests), and maintainability (implementation changes do not propagate to dependent classes).

Remember one sentence: depend on interfaces (not concrete classes), inject dependencies (do not create them with new), and this gives you swappability, testability, and maintainability through loose coupling.

---

Share
Use ← → arrow keys to navigate