TypeScript · Course navigation

TypeScript abstract classes and access modifiers

Use private, protected, readonly, and abstract to describe what subclasses and outside code may do.

On this page
Before you start

TypeScript classes and interfaces.

  • Use private, protected, readonly, and abstract to describe what subclasses and outside code may do.
  • Predict the sample output and explain the state changes.
  • Complete the practice task and check the boundary cases.

The concept

TypeScript adds access modifiers to classes. A private member is visible only inside the class, a protected member is also visible to subclasses, and public is the default. readonly forbids assignment after construction. These modifiers are checked by the compiler and erased at run time. For hard privacy at run time use JavaScript's #private fields.

An abstract class cannot be instantiated and may declare abstract members that subclasses must implement. Code can depend on the abstract type, for example Shape, and work with any subclass. A class can also implement an interface, which lists the members it promises to provide, without sharing any code. Fields are declared explicitly and assigned in the constructor.

Code example

TypeScript
interface Describable {
  describe(): string;
}

abstract class Shape implements Describable {
  readonly name: string;
  private static count = 0;

  constructor(name: string) {
    this.name = name;
    Shape.count++;
  }

  abstract area(): number;

  describe(): string {
    return this.name + " with area " + this.area().toFixed(2);
  }

  static total(): number {
    return Shape.count;
  }
}

class Circle extends Shape {
  private radius: number;

  constructor(radius: number) {
    super("circle");
    this.radius = radius;
  }

  area(): number {
    return Math.PI * this.radius ** 2;
  }
}

class Rectangle extends Shape {
  private width: number;
  private height: number;

  constructor(width: number, height: number) {
    super("rectangle");
    this.width = width;
    this.height = height;
  }

  area(): number {
    return this.width * this.height;
  }
}

const shapes: Shape[] = [new Circle(1.5), new Rectangle(2, 3.5)];
for (const shape of shapes) {
  console.log(shape.describe());
}
console.log("shapes created: " + Shape.total());
Output
circle with area 7.07
rectangle with area 7.00
shapes created: 2

Follow the code

Open the walkthrough to inspect the example one step at a time.

Show guided walkthrough
Guided walkthroughReady
1interface Describable {
2 describe(): string;
3}
4
5abstract class Shape implements Describable {
6 readonly name: string;
7 private static count = 0;
8
9 constructor(name: string) {
10 this.name = name;
11 Shape.count++;
12 }
13
14 abstract area(): number;
15
16 describe(): string {
17 return this.name + " with area " + this.area().toFixed(2);
18 }
19
20 static total(): number {
21 return Shape.count;
22 }
23}
24
25class Circle extends Shape {
26 private radius: number;
27
28 constructor(radius: number) {
29 super("circle");
30 this.radius = radius;
31 }
32
33 area(): number {
34 return Math.PI * this.radius ** 2;
35 }
36}
37
38class Rectangle extends Shape {
39 private width: number;
40 private height: number;
41
42 constructor(width: number, height: number) {
43 super("rectangle");
44 this.width = width;
45 this.height = height;
46 }
47
48 area(): number {
49 return this.width * this.height;
50 }
51}
52
53const shapes: Shape[] = [new Circle(1.5), new Rectangle(2, 3.5)];
54for (const shape of shapes) {
55 console.log(shape.describe());
56}
57console.log("shapes created: " + Shape.total());

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
Shape cannot be instantiated. It promises the Describable interface.—
—
Every concrete shape must implement area.—
—
describe is written once and calls the subclass's area.—
—
The array has the abstract type but holds the two subclasses.—
—
Each shape reports its own area: π·1.5² and 2·3.5.—
circle with area 7.07
rectangle with area 7.00
The static counter lives on the class and is private, with a public accessor.—
circle with area 7.07
rectangle with area 7.00
shapes created: 2

Behind the scenes

How TypeScript processes this code

TypeScript checks types and emits JavaScript. Type annotations are removed before execution.

Cost and common mistakes

describe and area are O(1).

  • private and protected exist only in the compiler. At run time anyone can read the property. Use #private for real privacy.
  • Parameter properties (constructor(private radius: number)) are shorter, but this example writes the fields out so that it also runs in type-stripping environments.
  • An interface has no code and no run-time presence. A class that only needs a contract should implement an interface rather than extend a class.

Try it yourself

Why make Shape abstract instead of a normal class?

Reveal answer and reasoning

A plain Shape has no meaningful area. Abstract forbids creating one and forces each subclass to define its area.

Further reading