Bird
Raised Fist0
LLDsystem_design~7 mins

Program to interface not implementation in LLD - System Design Guide

Choose your learning style10 modes available
Problem Statement
When code depends directly on concrete classes, changing implementations requires modifying all dependent code. This tight coupling makes the system fragile and hard to extend or test.
Solution
This pattern suggests coding against abstract interfaces rather than concrete implementations. Clients interact with interfaces, allowing implementations to change without affecting client code, promoting flexibility and easier maintenance.
Architecture
Client
→Interface
↓
ImplementationA

The diagram shows the client depending only on the interface, while multiple implementations can vary independently.

Trade-offs
✓ Pros
→
Enables swapping implementations without changing client code.
→
Improves testability by allowing mock implementations.
→
Reduces coupling, making code easier to maintain and extend.
✗ Cons
→
Introduces additional abstraction layers, which can increase complexity.
→
Requires upfront design of interfaces, which may slow initial development.
→
May lead to over-engineering if used unnecessarily in simple cases.
Use when multiple implementations are expected or when you want to isolate clients from changes in implementation. Suitable for medium to large codebases with evolving requirements.
Avoid when the system is very simple or unlikely to change, as the added abstraction may complicate the design unnecessarily.
Real World Examples
Amazon
Uses interfaces for payment processing to support multiple payment gateways without changing order processing code.
Netflix
Defines interfaces for data storage to switch between different databases or caches transparently.
Uber
Implements interfaces for different map providers, allowing easy replacement or addition of map services.
Code Example
The before code tightly couples OrderService to StripeProcessor, making it hard to change payment methods. The after code defines a PaymentProcessorInterface and programs to it, allowing OrderService to work with any payment processor implementation passed in, improving flexibility and testability.
LLD
### Before: Program to implementation (tight coupling)
class PaymentProcessor:
    def pay(self, amount):
        print(f"Processing payment of {amount} using Stripe")

class OrderService:
    def __init__(self):
        self.processor = PaymentProcessor()

    def checkout(self, amount):
        self.processor.pay(amount)

order = OrderService()
order.checkout(100)


### After: Program to interface (loose coupling)
from abc import ABC, abstractmethod

class PaymentProcessorInterface(ABC):
    @abstractmethod
    def pay(self, amount):
        pass

class StripeProcessor(PaymentProcessorInterface):
    def pay(self, amount):
        print(f"Processing payment of {amount} using Stripe")

class PaypalProcessor(PaymentProcessorInterface):
    def pay(self, amount):
        print(f"Processing payment of {amount} using PayPal")

class OrderService:
    def __init__(self, processor: PaymentProcessorInterface):
        self.processor = processor

    def checkout(self, amount):
        self.processor.pay(amount)

stripe = StripeProcessor()
order = OrderService(stripe)
order.checkout(100)

paypal = PaypalProcessor()
order2 = OrderService(paypal)
order2.checkout(200)
OutputSuccess
Alternatives
Program to implementation
Clients depend directly on concrete classes rather than interfaces.
Use when: Use only in very simple or throwaway code where flexibility is not needed.
Dependency Injection
Focuses on how implementations are provided to clients, often used together with programming to interfaces.
Use when: Use when you want to manage object creation and dependencies externally for better modularity.
Summary
Programming to interface reduces coupling by making clients depend on abstractions, not concrete classes.
This approach improves flexibility, testability, and maintainability of code.
It is best used when multiple implementations or future changes are expected.

Practice

(1/5)
1. What is the main benefit of programming to an interface rather than a concrete implementation?
easy
A. It allows changing the implementation without affecting the code that uses it.
B. It makes the program run faster by using direct calls.
C. It reduces the number of lines of code needed.
D. It forces the use of only one implementation.

Solution

  1. Step 1: Understand interface vs implementation

    Programming to an interface means using a contract that defines what methods are available, not how they work internally.
  2. Step 2: Identify the benefit of this approach

    This allows swapping or changing the underlying implementation without changing the code that depends on the interface.
  3. Final Answer:

    It allows changing the implementation without affecting the code that uses it. -> Option A
  4. Quick Check:

    Interface abstraction = flexibility [OK]
Hint: Interfaces hide details, so implementations can change freely [OK]
Common Mistakes:
  • Thinking it improves speed directly
  • Believing it reduces code size automatically
  • Assuming only one implementation is allowed
2. Which of the following code snippets correctly shows programming to an interface in a language like Java?
easy
A. List<String> list = new ArrayList<>();
B. ArrayList<String> list = new ArrayList<>();
C. List<String> list = new List<>();
D. ArrayList<String> list = new List<>();

Solution

  1. Step 1: Identify interface and implementation

    In Java, List is an interface, and ArrayList is a concrete class implementing List.
  2. Step 2: Check which declaration uses interface type

    Declaring the variable as List and instantiating with ArrayList means programming to interface, not implementation.
  3. Final Answer:

    List<String> list = new ArrayList<>(); -> Option A
  4. Quick Check:

    Variable type = interface [OK]
Hint: Use interface type on left side, implementation on right [OK]
Common Mistakes:
  • Using concrete class on both sides
  • Trying to instantiate an interface directly
  • Mixing types incorrectly
3. Given the following code in a language supporting interfaces:
interface Vehicle { void drive(); }
class Car implements Vehicle { public void drive() { print("Car driving"); } }
class Bike implements Vehicle { public void drive() { print("Bike driving"); } }
Vehicle v = new Car();
v.drive();

What will be the output?
medium
A. Compilation error
B. Bike driving
C. Car driving
D. Vehicle driving

Solution

  1. Step 1: Identify the object type assigned to interface variable

    The variable v of type Vehicle is assigned a new Car object.
  2. Step 2: Determine which drive() method runs

    Calling v.drive() runs the Car's drive() method because the actual object is Car.
  3. Final Answer:

    Car driving -> Option C
  4. Quick Check:

    Interface variable calls actual object's method [OK]
Hint: Interface calls use actual object's method at runtime [OK]
Common Mistakes:
  • Assuming interface method runs
  • Confusing object type with variable type
  • Expecting compilation error due to interface
4. Consider this code snippet:
interface Printer { void print(); }
class LaserPrinter implements Printer { void print() { System.out.println("Laser printing"); } }
Printer p = new LaserPrinter();
p.print();

Why does this code cause a compilation error?
medium
A. Interfaces cannot be instantiated.
B. LaserPrinter does not implement print() method.
C. Printer cannot be used as a variable type.
D. The print() method in LaserPrinter is not declared public.

Solution

  1. Step 1: Check method visibility in implementation

    Interface methods are public by default, so implementing methods must be public.
  2. Step 2: Identify method declaration in LaserPrinter

    LaserPrinter's print() method lacks the public modifier, causing a visibility mismatch error.
  3. Final Answer:

    The print() method in LaserPrinter is not declared public. -> Option D
  4. Quick Check:

    Interface methods require public implementation [OK]
Hint: Implement interface methods as public to avoid errors [OK]
Common Mistakes:
  • Thinking interfaces can't be variable types
  • Believing missing method causes error instead
  • Ignoring method visibility rules
5. You are designing a payment system where multiple payment methods (CreditCard, PayPal, Crypto) can be used. To follow the principle 'Program to interface not implementation', which design is best?
hard
A. Write separate classes with no common interface and call their methods directly.
B. Create a PaymentMethod interface with a pay() method, and implement it in each payment class.
C. Use a single PaymentProcessor class with if-else to handle all payment types.
D. Make all payment classes inherit from a concrete PaymentProcessor class.

Solution

  1. Step 1: Identify the need for abstraction

    Multiple payment types require a common contract to allow flexible usage.
  2. Step 2: Choose design that uses interface

    Defining a PaymentMethod interface with pay() method lets each payment type implement its own logic while code depends only on the interface.
  3. Final Answer:

    Create a PaymentMethod interface with a pay() method, and implement it in each payment class. -> Option B
  4. Quick Check:

    Interface abstraction enables flexible, extendable design [OK]
Hint: Use interface for common behavior, implementations for details [OK]
Common Mistakes:
  • Hardcoding payment types with if-else
  • Not using interfaces for common behavior
  • Using inheritance from concrete classes
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard