TypeScript Tutorial

TypeScript Polymorphism

A base type can hold a derived object. Call methods through the base and the derived version runs.

One call, the right version

You have an Animal and two kinds of animal. You want speak() to mean bark for a dog and meow for a cat, even when the code that calls speak only knows it has an Animal.

That is polymorphism: the same name, different behavior chosen from the real type of the object. In TypeScript you store a derived object in a base-typed variable and mark the replacement with override. Methods are already virtual. You do not write a virtual keyword.

override speak()

Write the method on the base class. In the derived class, write the same signature and addoverride. override asks the compiler to check that you really replaced a base method. A typo in the name then fails at compile time instead of silently adding a new method.

Example

class Animal {
  public speak(): void {
    console.log("...");
  }
}

class Dog extends Animal {
  public override speak(): void {
    console.log("woof");
  }
}

class Cat extends Animal {
  public override speak(): void {
    console.log("meow");
  }
}

const d: Dog = new Dog();
const c: Cat = new Cat();
d.speak();
c.speak();

Direct calls on d and c already pick the derived version. Polymorphism matters most when the static type is the base.

A base variable holds a derived object

Create a Dog and store it in an Animal variable. Call speak through that variable. The program runs Dog.speak, not the base body.

Example

class Animal {
  public speak(): void {
    console.log("...");
  }
}

class Dog extends Animal {
  public override speak(): void {
    console.log("woof");
  }
}

const d: Dog = new Dog();
const p: Animal = d;
p.speak();

p is typed as Animal. The object is still a Dog. You can also writeconst p: Animal = new Dog(); in one step. The variable’s type is the base. The value’s type is the derived class.

Through p you can call methods that exist on Animal. You cannot callbark on p unless bark is also declared on the base. The compiler looks at the variable’s type.

Several types, one loop

Put different animals in an array typed as Animal[]. The loop only knows Animal. Each object still speaks as itself.

Example

class Animal {
  public speak(): void {
    console.log("...");
  }
}

class Dog extends Animal {
  public override speak(): void {
    console.log("woof");
  }
}

class Cat extends Animal {
  public override speak(): void {
    console.log("meow");
  }
}

const zoo: Animal[] = [new Dog(), new Cat()];
for (const a of zoo) {
  a.speak();
}

That is the payoff. Drawing code, UI widgets, and game entities all use this pattern: store base-typed values, call one method, get the derived work.

abstract when the base has no real body

Sometimes the base should not be constructed at all. Mark the class abstract and mark the methodabstract with no body. Derived classes must implement it. new Shape() then fails to compile; new Square(4) is the object you actually create.

Example

abstract class Shape {
  public abstract area(): number;
}

class Square extends Shape {
  private side: number;

  constructor(side: number) {
    super();
    this.side = side;
  }

  public override area(): number {
    return this.side * this.side;
  }
}

class Circle extends Shape {
  private radius: number;

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

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

const shapes: Shape[] = [new Square(4), new Circle(1)];
for (const s of shapes) {
  console.log(s.area());
}

Each value in shapes is a Shape as far as the loop knows. The square prints 16. The circle prints a number near 3.14. A later chapter stays on abstract classes. Here they are the strict form of “call through the base.”

Rules that keep this small

  • Store derived objects in a base-typed variable or array when the caller should not care which subclass it has.
  • Write override on the replacement. tsc then checks the name and the parameter list.
  • The signatures must match: return type, name, and parameters.
  • Use abstract on the base when there is no sensible default body.

Compile these at /typescript/try. Next: splitting a program across files withexport and import.

FAQ: TypeScript Polymorphism

Common questions about this page.

What is the StudyGrid TypeScript tutorial?

The StudyGrid TypeScript tutorial follows the same chapter rhythm as C++: syntax, types, input, loops, functions, classes, generics, maps, and lambdas. Each chapter has copy-and-run examples.

Should I run typescript polymorphism examples locally for better learning?

Yes. Use the browser editor on StudyGrid for a quick check, then Download the example and run it on your computer. Local runs show real errors and the real toolchain, which is one of the fastest ways to learn typescript polymorphism in this TypeScript TypeScript lesson (TypeScript Polymorphism).

Is the TypeScript editor the same as Try Python or Try C++?

No. Try TypeScript type-checks with tsc at /typescript/try and shows stdout plus compiler messages. Try Python stays at /try. Try C++ stays at /cpp/try. TypeScript lessons never open those editors.

Do I need to install a compiler to learn TypeScript?

No. Open a chapter, click Try it in TypeScript, and compile in the browser. You can also download a .ts file and compile locally with tsc.

Where should I start the TypeScript tutorial?

Start at TypeScript Intro, then Get Started and Syntax. After the first program, continue to output, variables, and if-else. After classes, open TypeScript Examples, then generics, Map, and arrow functions. Use Next at the bottom of each chapter.

Is the TypeScript tutorial free?

Yes. The TypeScript workshop on StudyGrid (studygrid.in) is free: dashboard, chapters, and the compile-and-run editor.