Inheritance & Abstract Classes

Intermediate · 21 min read · ▶ live playground · ✦ checkpoint

Inheritance lets one class start from another class: extends links the subclass to the base class, super calls the base constructor or method, and override asks TypeScript to prove you really replaced an existing method. Abstract classes go one step further: they can share real code while leaving specific members for subclasses to implement.

This is useful, but it is also easy to overuse. Keep your mental model small: inheritance is for an "is a more specific kind of" relationship, not for every bit of code reuse.

extends, super, and override

A class that extends another class inherits its public and protected members. The base class handles the shared state and behavior; the subclass adds or changes the specific parts.

When a subclass has its own constructor, it must call super(...) before using this. That call runs the base constructor so the base part of the object is initialized.

typescript — playgroundtype-checked
⌘/Ctrl + Enter to run

VideoLessonCard is a BaseLessonCard with one extra field. Its constructor sends title and minutes upward through super(title, minutes), then initializes platform itself.

The override keyword is small but valuable. Without it, a typo can silently create a new method instead of replacing the old one. With it, TypeScript checks the relationship.

The errors are trying to protect construction

Here is the missing-super version. The subclass touches this.minutes, but the base constructor has not run yet:

class BaseLessonCard {
  title: string;
 
  constructor(title: string) {
    this.title = title;
  }
}
 
class VideoLessonCard extends BaseLessonCard {
  minutes: number;
 
  constructor(title: string, minutes: number) {
    this.minutes = minutes;
  }
}

→ playground.ts:12:3 - error TS2377: Constructors for derived classes must contain a 'super' call. → playground.ts:13:5 - error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class.

And here is the typo override catches:

class BaseLessonCard {
  summary(): string {
    return "lesson";
  }
}
 
class VideoLessonCard extends BaseLessonCard {
  override sumarize(): string {
    return "video";
  }
}

→ playground.ts:8:12 - error TS4113: This member cannot have an 'override' modifier because it is not declared in the base class 'BaseLessonCard'.

Read both errors as design feedback, not ceremony. super says "build the base object first." override says "prove this method is replacing something real."

Abstract classes

An abstract class is a class you cannot construct directly. It can contain real fields and methods, and it can also declare abstract members: method or field promises that concrete subclasses must fill in.

Use an abstract class when the base has shared implementation, not just a shape. If you only need a shape, 6.2's implements pattern and Section 5's object types are often enough.

typescript — playgroundtype-checked
⌘/Ctrl + Enter to run

CourseWork owns the shared title field and the shared describe() method. It cannot know how to estimate every kind of work, so estimateMinutes() is abstract. ReadingWork and PracticeWork must implement it before they become concrete classes.

The workItems loop is normal class polymorphism: a variable typed as CourseWork can hold a ReadingWork or a PracticeWork, and work.describe() dispatches to the right estimateMinutes() implementation at runtime.

The printWork example shows TypeScript's structural flavor. It accepts the shape { describe(): string }, so both a class instance and the plain officeHours object can pass. No shared base class is required when the shape is all you need.

If a concrete subclass skips an abstract member, TypeScript points at the class:

abstract class CourseWork {
  abstract estimateMinutes(): number;
}
 
class ReadingWork extends CourseWork {}

→ playground.ts:5:7 - error TS2515: Non-abstract class 'ReadingWork' does not implement inherited abstract member estimateMinutes from class 'CourseWork'.

Inheritance is not wrong. It is just heavier than it first looks. Use it when the shared implementation is real and the relationship is stable; let structural shapes handle cases where all you need is "this value has the right method."

Next lesson closes the section by asking when classes earn their keep and when plain objects plus functions make a simpler TypeScript design.

Checkpoint

Answer all three to mark this lesson complete

+50 XP on completion