Low-Level Design Tutorial 0/42 lessons ~6 min read Lesson 15

    Dependency Inversion Principle

    The Dependency Inversion Principle (DIP) says high-level modules should not depend on low-level modules; both depend on abstractions.

    Course progress0%
    Focus
    9 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    The Dependency Inversion Principle (DIP) says high-level modules should not depend on low-level modules; both depend on abstractions. OrderService depends on PaymentGateway interface, not StripeClient concrete class.

    DIP is how LLD designs stay testable: inject in-memory fakes in unit tests, swap Redis for map in interviews. Constructor injection in Java is the standard pattern interviewers expect.

    Combine DIP with factories when construction logic is non-trivial.

    Understanding the topic

    Key concepts

    • High-level policy independent of low-level details.
    • Abstractions owned by domain layer, not infrastructure.
    • Dependency injection: constructor > setter > field.
    • Inversion of Control containers in Spring; manual wiring in LLD.
    • DIP enables mocking external systems in tests.
    • Do not invert everything — stable utilities may be direct deps.
    text
    flowchart LR
    SRP --> OCP --> LSP --> ISP --> DIP

    Step-by-step explanation

    1. Identify high-level orchestrator (ATM, BookingService).
    2. Extract interfaces for IO, persistence, payment, clock.
    3. Implement concretes in infrastructure package.
    4. Wire via constructor at composition root.
    5. Use factory when choosing implementation at runtime.
    6. Keep domain free of JDBC, HTTP, file path strings.

    Informative example

    ATM depends on abstractions for hardware and persistence:

    java
    public interface CashDispenser {
    boolean dispense(Money amount);
    }
    public interface TransactionStore {
    void save(Transaction txn);
    }
    public final class AtmService {
    private final CashDispenser dispenser;
    private final TransactionStore store;
    public AtmService(CashDispenser dispenser, TransactionStore store) {
    this.dispenser = dispenser;
    this.store = store;
    }
    public Result withdraw(Account account, Money amount) {
    if (!account.debit(amount)) return Result.failed("insufficient funds");
    if (!dispenser.dispense(amount)) {
    account.credit(amount);
    return Result.failed("dispenser error");
    }
    store.save(Transaction.withdraw(account.id(), amount));
    return Result.ok();
    }
    }

    AtmService knows nothing about USB hardware or SQL — interview tests pass with fakes.

    Real-world use

    Real-world applications

    • Every LLD service orchestrating external systems.
    • Unit testing without databases or network.
    • Swapping implementations per environment.

    Best practices

    • Constructor inject required dependencies as final fields.
    • Program to interfaces in field types.
    • Composition root wires concrete graph once.
    • Avoid service locator anti-pattern in domain code.
    • Keep interfaces in domain package; impls in infra.

    Common mistakes

    • new StripeClient() inside business method.
    • Interfaces owned by infrastructure leaking upward.
    • Injecting concrete List when abstraction unnecessary.
    • Circular dependencies between high-level modules.

    Advanced interview questions

    Q1BeginnerWhat is DIP?
    Depend on abstractions, not concretions; high-level modules define interfaces they need.
    Q2BeginnerHow is DIP different from DI?
    DIP is the principle; DI is the technique (constructor injection) implementing it.
    Q3IntermediateWhy constructor injection?
    Immutable dependencies, explicit requirements, easy testing.
    Q4IntermediateWhen can you depend on a concrete class?
    Stable value objects or JDK types with no variation (String, Duration).
    Q5AdvancedDesign DIP for movie booking with external payment.
    BookingService depends on PaymentGateway and SeatRepository interfaces; adapters implement Stripe and JDBC.

    Summary

    High-level code depends on abstractions it owns. Constructor injection is the Java LLD default. DIP enables tests with fakes and mocks. Infrastructure implements domain interfaces. SOLID together produces maintainable object graphs.

    Ready to mark this lesson complete?Track your journey across the entire course.