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
| 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) |
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” —
Caris aVehicle
Use an interface when:
- We just need to define a contract (what methods must exist)
- A class needs to fulfill multiple roles (
PrintableANDExportable) - Unrelated classes need the same capability — a
Documentand anImagecan both beExportable
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.