What an abstract class is
An abstract class is a class that exists to be extended, never to be created directly. It describes what a family of classes has in common and leaves some parts open for each member of the family to fill in. Think of Animal: every animal can speak(), but what that sounds like depends on whether it is a dog or a cat. There is no sensible way to speak as "just an animal".
TypeScript gives you the abstract keyword for this. You put it on the class, and on every method that subclasses must provide:
abstract class Animal {
abstract speak(): void;
}
An abstract method has a signature and no body, just a semicolon. The signature is the contract: the name, the parameters and the return type that every concrete subclass has to match.
The two rules the compiler enforces
- You can't create an instance of an abstract class. Writing
new Animal()gives the errorCannot create an instance of an abstract class. - A concrete subclass must implement every abstract method it inherits. If a class extends
Animaland doesn't definespeak, the compiler reportsNon-abstract class 'Cat' does not implement inherited abstract member speak from class 'Animal'.
Together these make the abstract class a contract with teeth. Any code that receives an Animal can call speak() knowing that a real implementation exists behind it.
The lesson's exercise
The starter code already declares the abstract Animal class and, at the bottom, two lines that create a Dog named rex and call its speak method. Your job is to write the missing Dog class so that running the program prints Woof.
- Declare
Dogwithextends Animal. The code at the bottom needs a class it can instantiate, andAnimalitself is off limits, so the program needs a concrete subclass. - Implement
speak(): voidinside it. The method must match the abstract signature: same name, no parameters, and avoidreturn type. Its body logs the text withconsole.log.
The finished program:
abstract class Animal {
abstract speak(): void;
}
class Dog extends Animal {
speak(): void {
console.log("Woof");
}
}
const rex = new Dog();
rex.speak();
It prints:
Woof
Notice that Dog declares no constructor. A subclass without one inherits its parent's constructor, and Animal has none of its own, so new Dog() works with no arguments.
Mixing abstract and concrete members
An abstract class doesn't have to be all blanks. It can hold fields, a constructor and ordinary methods with bodies, and every subclass inherits them. Only the members marked abstract need an override. This is where abstract classes earn their keep: the shared code lives in one place, and the parts that vary are forced into the subclasses.
abstract class Shape {
constructor(public name: string) {}
abstract area(): number;
describe(): string {
return `${this.name} with area ${this.area()}`;
}
}
class Square extends Shape {
constructor(private side: number) {
super("Square");
}
area(): number {
return this.side * this.side;
}
}
console.log(new Square(3).describe());
This prints:
Square with area 9
describe() is written once, in Shape, and calls area(), which each subclass defines in its own way. Because Square declares a constructor, it must call super(...) first to run the parent's constructor. Here public name and private side in the parameter lists are TypeScript shorthand for declaring a field and assigning it from the argument.
What happens at run time
The abstract keyword exists only for the compiler. When TypeScript turns your code into JavaScript, the modifier is removed, and so are the abstract method declarations. The output has a normal class Animal with no speak in it, and nothing in it stops anyone from creating one.
That has two consequences worth knowing:
- The compiler reports the error for
new Animal(), but by default it still writes the JavaScript file. If that file runs, the object gets created. Treat compiler errors as real errors and fix them. - Type checks like these protect you only while the code is checked. If an instance of an incomplete class slips through, for example via a cast with
as any, the failure shows up later asspeak is not a function, because the method never existed in the generated JavaScript.
If you need a hard guarantee at run time, you can check in the constructor with new.target, or throw an error from a base-class method. For most code, the compile-time check is the point of the feature.
Common mistakes
- Forgetting to implement an abstract method. The class fails to compile with the "does not implement inherited abstract member" error. Add the method, or mark the new class
abstracttoo if it is meant to stay incomplete. - Using a different signature. If
speakis declared to returnvoidand a subclass's version needs a parameter the contract doesn't have, callers holding anAnimalcan't use it safely, so the compiler objects. - Giving an abstract method a body.
abstract speak() { ... }is an error. A body belongs in the subclass. - Calling
newon the abstract class. Instantiate a subclass, then store it in a variable typed as the abstract class if you want code that works for all of them:const pet: Animal = new Dog();. - Forgetting
super(). A subclass that defines its own constructor must callsuper(...)before usingthis.
Key takeaways
- An
abstract classis a base class you can extend but not instantiate. - An
abstractmethod has a signature and no body; each concrete subclass must implement it. - Abstract classes can also carry fields and ordinary methods that every subclass inherits.
- The checks happen at compile time. The generated JavaScript has no trace of
abstract.