4.5 — Abstract Classes
Some blueprints are meant to be extended, not built — meet abstract classes!
Some classes are never meant to be instantiated directly. They exist purely as parent classes — they define the shape and partial behavior, and their subclasses fill in the remaining pieces. These are called abstract classes.
Think of the Nizam's Master Architect. He draws up a "Generic Palace Plan." This master plan says: every palace MUST have a grand entrance and a durbar hall — these are already fully designed, with the exact number of pillars, the arch shapes, the chandelier placements. These are your concrete methods — implemented, ready to use.
BUT the master plan also says: every palace must ALSO have a garden. And the architect declares: "I don't know HOW you design your garden, but you MUST have one." That's an abstract method — a requirement without an implementation. A signature without a body. A blank that MUST be filled.
Here's the crucial part: you cannot build a "Generic Palace." The architect won't allow it. The municipality won't approve it. new GenericPalace() is illegal! You MUST build a specific palace — a subclass — that provides the garden design.
Chowmahalla Palace builds a beautiful Mughal garden. Falaknuma Palace builds an elegant Japanese garden. Both satisfy the abstract requirement — both have a garden — but each fulfills it in their own unique way. The master plan is satisfied, and each palace is distinct.
An abstract class, therefore, is a partial blueprint plus mandatory blanks to fill. It gives you shared structure and shared behavior, but it forces every subclass to provide specific implementations for the abstract parts. This is incredibly powerful for designing class hierarchies where some behavior is universal and some is specific.
TypeScript uses the abstract keyword to mark both the class and any methods that have no implementation. Let's see the full syntax:
abstract class Palace {
// Abstract method - no body!
abstract getGardenType(): string;
// Concrete method - has body
describe(): string {
return "A grand palace";
}
}
The abstract keyword before the class name tells TypeScript: "This class is incomplete. Nobody can create an instance of it." The abstract keyword before getGardenType() tells TypeScript: "This method has no body. Any concrete subclass MUST implement it."
Now watch what happens if you try to create an instance directly:
// ❌ ERROR!
const p = new Palace();
// Cannot create an instance of
// an abstract class.
TypeScript stops you cold. You must create a concrete subclass:
class Chowmahalla extends Palace {
getGardenType(): string {
return "Mughal Garden";
}
}
// ✅ This works!
const c = new Chowmahalla();
console.log(c.getGardenType());
// "Mughal Garden"
console.log(c.describe());
// "A grand palace"
Notice something beautiful: Chowmahalla automatically inherits the describe() method from Palace. It only needs to implement the abstract method. The concrete methods are free!
You can also have abstract properties:
abstract class Shape {
abstract area: number;
describe(): string {
return "Area: " + this.area;
}
}
Now here's an important rule about the chain of responsibility: abstract methods MUST be implemented by the first concrete subclass. If an intermediate subclass is also abstract, it can "pass the buck" — it doesn't need to implement the parent's abstract methods. But somewhere down the line, a concrete class MUST implement ALL inherited abstract methods. No escaping!
abstract class Building {
abstract getStyle(): string;
}
// Still abstract - passes the buck
abstract class Palace extends Building {
abstract getGardenType(): string;
}
// Concrete - must implement BOTH
class Falaknuma extends Palace {
getStyle(): string {
return "Italian + Mughal";
}
getGardenType(): string {
return "Japanese Garden";
}
}
Falaknuma must implement both getStyle() (from Building) and getGardenType() (from Palace), because it's the first concrete class in the chain. This ensures no abstract requirement is left unfulfilled when an object is actually created.
Let's do a proper three-way comparison — this is where most developers get confused. Understanding when to use which is a superpower.
- Interface: Only describes shape — no implementation at all. A class can implement multiple interfaces. Think of it as a pure contract — "I promise these methods will exist."
- Abstract class: Describes shape AND provides partial implementation. A class can extend only ONE abstract class. Think of it as a semi-built structure — some rooms are finished, others are just marked "to be completed."
- Concrete class: Full implementation. Can be instantiated directly. Think of it as a fully constructed building — ready to move in.
When to use each:
- Interface — when you just need a contract with no shared code. Multiple classes from different hierarchies need to agree on the same shape.
- Abstract class — when you have shared logic that ALL subclasses should inherit, PLUS requirements each subclass must fulfill individually. This is your go-to for family hierarchies.
- Concrete class — when everything is fully implemented and there are no mandatory blanks.
Here's a powerful real-world example — the Template Method pattern:
abstract class Vehicle {
// Subclass must define this
abstract fuelType(): string;
// Shared logic for ALL vehicles
startEngine(): void {
console.log(
"Starting " + this.fuelType()
);
}
}
class Auto extends Vehicle {
fuelType(): string {
return "CNG";
}
}
class Metro extends Vehicle {
fuelType(): string {
return "Electricity";
}
}
Both Auto and Metro share the startEngine() logic — they don't rewrite it. But each defines its own fuelType(). When you call auto.startEngine(), it prints "Starting CNG". When you call metro.startEngine(), it prints "Starting Electricity". The abstract class provides the skeleton, and the subclasses fill in the details.
This is the Template Method pattern — one of the most elegant uses of abstract classes. The parent class defines the algorithm's structure (the template), and the subclasses provide the specific steps. You get code reuse AND flexibility. No interface can give you this, because interfaces have no implementation to share!
Key differences summary:
- Interfaces: multiple inheritance, no code, just shape
- Abstract classes: single inheritance, some code + mandatory blanks
- Concrete classes: instantiable, all code present
Abstract classes come with a set of common mistakes that trip up even experienced developers. Let's walk through each one so you can avoid them.
Trap 1: Forgetting to implement an abstract method.
abstract class A {
abstract doIt(): void;
}
// ❌ ERROR!
class B extends A {}
// Non-abstract class 'B' does not
// implement inherited abstract
// member 'doIt' from class 'A'.
Class B must either implement doIt() or be declared abstract itself. There's no middle ground — you can't leave an abstract method dangling.
Trap 2: Trying to instantiate an abstract class.
abstract class A {
abstract doIt(): void;
}
// ❌ ERROR! Always!
const a = new A();
// Cannot create an instance of
// an abstract class.
This will never work. Abstract classes are blueprints, not buildings. You can't move into a blueprint!
Trap 3: Putting an abstract method in a non-abstract class.
// ❌ ERROR!
class A {
abstract doIt(): void;
}
// Abstract methods can only
// appear within an abstract class.
Only abstract classes can have abstract methods. A regular (concrete) class must provide implementations for all its methods. If you need an abstract method, the class itself must be abstract.
Trap 4: Not calling super() in the subclass constructor.
abstract class Base {
constructor(public name: string) {}
}
class Child extends Base {
constructor() {
// ❌ ERROR if super() missing!
// Constructors of derived
// classes must contain super()
super("Default");
}
}
If the abstract class has a constructor with parameters, the subclass must call super() with the appropriate arguments. TypeScript enforces this strictly.
Trap 5: Access modifier mismatches.
abstract class Parent {
protected abstract doWork(): void;
}
class Child extends Parent {
// ✅ VALID - widening access
public doWork(): void {}
}
abstract class Parent2 {
public abstract doWork(): void;
}
class Child2 extends Parent2 {
// ❌ ERROR - narrowing access
protected doWork(): void {}
}
Widening access (from protected to public) is allowed. Narrowing (from public to protected) is NOT. The subclass method must be at least as accessible as the parent's.
Trap 6: Thinking abstract classes replace interfaces.
They don't! Interfaces support multiple inheritance; abstract classes don't. Use them together for maximum power:
interface Loggable {
log(): void;
}
abstract class Base {
abstract compute(): number;
}
class Smart extends Base
implements Loggable {
compute() { return 42; }
log() { console.log("done"); }
}
Here, Smart gets the abstract class's shared logic AND the interface's contract. This is a common and powerful pattern — don't choose between them, use both!
Here's your quick-reference cheatsheet for abstract classes. Bookmark this — you'll come back to it!
Declaring an Abstract Class:
abstract class X {
// No body - subclass MUST implement
abstract method(): type;
// Has body - inherited as-is
concrete() {
// shared logic here
}
}
Extending an Abstract Class:
class Y extends X {
// Must implement all abstracts
method(): type {
// specific implementation
}
}
The Six Golden Rules:
- Rule 1: Cannot instantiate abstract classes.
new AbstractClass()is always an error. Abstract classes exist to be extended, not to be used directly. - Rule 2: Abstract methods have NO body — not even an empty one. Subclasses MUST implement them. There is no escape from this rule for concrete subclasses.
- Rule 3: Concrete methods in abstract classes ARE inherited by subclasses. This is the whole point — shared behavior that you don't have to rewrite.
- Rule 4: A class can extend ONE abstract class AND implement multiple interfaces simultaneously. Combine them for maximum flexibility.
- Rule 5: If a subclass is also abstract, it can skip implementing parent abstract methods. But the first concrete class in the chain must implement everything.
- Rule 6: Abstract = "I define the skeleton, you fill the blanks." This is the essence of the Template Method pattern.
Quick Comparison Table:
- Interface = contract only, no code, multiple inheritance. Best for: defining capabilities across unrelated classes.
- Abstract class = contract + shared code, single inheritance. Best for: family hierarchies with common behavior.
- Concrete class = full code, instantiable, single inheritance. Best for: complete, ready-to-use implementations.
The Golden Rule: "Abstract classes are the Master Architect's plan — common structure plus mandatory blanks. You can't build the generic plan — build a specific palace that fills in the garden!"
Key Points
- Abstract classes cannot be instantiated — they exist to be extended
- Abstract methods have no body — concrete subclasses MUST implement them
- Concrete methods in abstract classes ARE inherited by all subclasses
- A class extends ONE abstract class but can implement multiple interfaces
- The Template Method pattern uses abstract classes to define algorithm skeletons
- First concrete subclass in the chain must implement ALL inherited abstract methods
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login