Abstract Classes vs Interfaces

beginner 0-2 YOE lld oop abstract-class interface design

In LLD interviews, we often need to decide: should this be an abstract class or an interface? They look similar but serve different purposes. Let’s break it down.

What is an Abstract Class?

An abstract class is a class that can’t be instantiated directly. It provides a partial implementation — some methods are fully defined, others are left for subclasses to fill in.

Think of it like a recipe template. It tells us: “Here’s how to preheat the oven (done for you), but YOU decide what filling to use.”

from abc import ABC, abstractmethod

class Vehicle(ABC):
    def __init__(self, brand):
        self.brand = brand  # abstract classes CAN have state

    def start_engine(self):
        print("Turning key... engine on!")  # shared implementation

    @abstractmethod
    def fuel_type(self):
        pass  # subclasses MUST implement this

class Car(Vehicle):
    def fuel_type(self):
        return "Petrol"

class ElectricCar(Vehicle):
    def fuel_type(self):
        return "Electric"

# vehicle = Vehicle("Generic")  # TypeError -- can't instantiate
car = Car("Toyota")
car.start_engine()       # inherited: "Turning key... engine on!"
print(car.fuel_type())   # Car's own: "Petrol"
class Vehicle {
  constructor(brand) {
    if (new.target === Vehicle) {
      throw new Error("Can't instantiate Vehicle directly");
    }
    this.brand = brand;
  }

  startEngine() {
    console.log("Turning key... engine on!");
  }

  fuelType() {
    throw new Error("Subclass must implement fuelType()");
  }
}

class Car extends Vehicle {
  fuelType() {
    return "Petrol";
  }
}

class ElectricCar extends Vehicle {
  fuelType() {
    return "Electric";
  }
}

const car = new Car("Toyota");
car.startEngine();          // "Turning key... engine on!"
console.log(car.fuelType()); // "Petrol"
abstract class Vehicle {
    String brand;

    Vehicle(String brand) {
        this.brand = brand; // abstract classes CAN have state
    }

    void startEngine() {
        System.out.println("Turning key... engine on!"); // shared
    }

    abstract String fuelType(); // subclasses MUST implement
}

class Car extends Vehicle {
    Car(String brand) { super(brand); }
    String fuelType() { return "Petrol"; }
}

class ElectricCar extends Vehicle {
    ElectricCar(String brand) { super(brand); }
    String fuelType() { return "Electric"; }
}

What is an Interface?

An interface is a pure contract. It says “any class that implements me MUST have these methods” but provides no implementation and no state.

Think of it like a job description. It lists what skills are required, but doesn’t teach how to do the work.

from abc import ABC, abstractmethod

# Python uses ABCs as interfaces (no built-in interface keyword)
class Printable(ABC):
    @abstractmethod
    def print_details(self):
        pass

class Exportable(ABC):
    @abstractmethod
    def export_to_pdf(self):
        pass

# A class can implement MULTIPLE interfaces
class Invoice(Printable, Exportable):
    def print_details(self):
        print("Invoice #123 - $500")

    def export_to_pdf(self):
        print("Exporting invoice to PDF...")

invoice = Invoice()
invoice.print_details()
invoice.export_to_pdf()
// JS doesn't have interfaces natively
// We rely on duck typing or documentation

// "Interface" as a convention:
// Printable: must have printDetails()
// Exportable: must have exportToPdf()

class Invoice {
  printDetails() {
    console.log("Invoice #123 - $500");
  }

  exportToPdf() {
    console.log("Exporting invoice to PDF...");
  }
}

// In TypeScript, we'd use actual `interface` keyword:
// interface Printable { printDetails(): void; }
// interface Exportable { exportToPdf(): void; }
// class Invoice implements Printable, Exportable { ... }
interface Printable {
    void printDetails(); // no implementation, just the contract
}

interface Exportable {
    void exportToPdf();
}

// A class can implement MULTIPLE interfaces
class Invoice implements Printable, Exportable {
    public void printDetails() {
        System.out.println("Invoice #123 - $500");
    }

    public void exportToPdf() {
        System.out.println("Exporting invoice to PDF...");
    }
}

Key Differences

Abstract Class vs Interface
Feature Abstract Class Interface
State (fields) Can have instance variables No state (only constants)
Implementation Can have concrete methods Only method signatures*
Inheritance Single (one parent only) Multiple (many interfaces)
Constructor Can have constructors No constructors
Relationship "Is-a" (shared identity) "Can-do" (shared capability)
*Java 8+ allows default methods in interfaces, but the core idea still holds.

When to Use Which

Use an abstract class when:

  • Subclasses share common state (fields) or behavior (methods)
  • We want to provide a base implementation that subclasses extend
  • The relationship is “is-a” — Car is a Vehicle

Use an interface when:

  • We just need to define a contract (what methods must exist)
  • A class needs to fulfill multiple roles (Printable AND Exportable)
  • Unrelated classes need the same capability — a Document and an Image can both be Exportable

The Rule of Thumb

Here’s a simple mental model:

  • Abstract class = “Here’s a base thing. You’re a specialized version of it.”
  • Interface = “Here’s a capability. You can do this, regardless of what you are.”

A Dog extends Animal (abstract class — it IS an animal). A Dog implements Trainable (interface — it CAN be trained).

In LLD interviews, we’ll use interfaces heavily for defining contracts between components, and abstract classes when we have shared logic in a hierarchy. Most design patterns (Strategy, Observer, Factory) lean on interfaces because they care about what an object can do, not what it is.