Method overriding in TypeScript means that a subclass declares a method with the same name as a method in its parent class, and the subclass version replaces the parent version for objects of the subclass. The runtime picks the version, so even when the object is held in a variable of the parent type, calling the method runs the subclass code. Since TypeScript 4.3, we mark such methods with the override keyword, and the noImplicitOverride compiler option makes that keyword mandatory.
We override a method when a subclass needs its own version of a shared operation. For example, a checkout page calls pay() on every payment option, and the card and bank transfer subclasses each override pay() with their own steps.
Java developers know the idea from @Override. The TypeScript override keyword has the same purpose, but with noImplicitOverride turned on, the compiler checks both directions.
- A method marked override must exist in the parent.
- A method that replaces a parent method must be marked.
Every method can be overridden, because TypeScript has no final modifier for methods.
The following example overrides two methods of a Shape parent class in a Square subclass, compiled with TypeScript 7.0.2 (strict and noImplicitOverride turned on) and run on Node.js 22. The method describe() calls the parent version with super, and we call both methods through a variable of type Shape.
class Shape {
area(): number { return 0; }
describe(): string { return "area " + this.area(); }
}
class Square extends Shape {
side: number;
constructor(side: number) {
super();
this.side = side;
}
// 1. Override a method with the override keyword
override area(): number { return this.side * this.side; }
// 2. Call the parent version with super
override describe(): string { return "square, " + super.describe(); }
}
// 3. The subclass method runs, even through a Shape variable
const shape: Shape = new Square(4);
const area = shape.area(); // area = 16
const text = shape.describe(); // text = "square, area 16"
Notice that in the last line, super.describe() runs the code of Shape, but inside it, this.area() still calls the Square version, so the result is “area 16” and not “area 0”.
Next, we look at the override keyword with noImplicitOverride, calls to the parent version with super and the signature and visibility rules that the compiler enforces. The last section shows abstract methods, which every subclass must override.
1. Overriding a Method in a Subclass
A subclass inherits every method of its parent. When the parent’s version does not fit, the subclass declares a method with the same name and a compatible signature. Objects of the subclass use the new method, and objects of the parent class keep the old one.
Overriding is what makes a list of mixed shapes work. For example, a floor plan app sums the area of all rooms, whatever their shape. The shapes array has the type Shape[], and the code calls describe() on every element without knowing its class, so each object runs its own area().
class Shape {
name = "shape";
area(): number { return 0; }
describe(): string { return this.name + " with area " + this.area(); }
}
class Square extends Shape {
side: number;
constructor(side: number) {
super();
this.side = side;
this.name = "square";
}
override area(): number { return this.side * this.side; }
}
class Rectangle extends Shape {
width: number;
height: number;
constructor(width: number, height: number) {
super();
this.width = width;
this.height = height;
this.name = "rectangle";
}
override area(): number { return this.width * this.height; }
}
// Each object runs its own area(), also inside describe()
const shapes: Shape[] = [new Shape(), new Square(3), new Rectangle(2, 5)];
const texts = shapes.map((s) => s.describe());
// texts = ["shape with area 0", "square with area 9", "rectangle with area 10"]
The ability of one call to run different code depending on the object is called polymorphism. In JavaScript, every method call looks up the method on the object’s prototype chain at runtime, so overriding works the same way as virtual methods in Java. Adding a new shape means adding one class with its own area(), and neither describe() nor the loop changes. Overriding replaces the alternative, which is checking the class with instanceof and branching on it.
2. The override Keyword and noImplicitOverride
The override keyword, added in TypeScript 4.3, tells the compiler that a method is meant to replace a parent method. If no parent method with that name exists, the compiler reports an error. The error catches the most common overriding bug, a typo or a renamed parent method, which creates a new method without any warning instead of overriding the old one.
class Square extends Shape {
override aera(): number { return 16; } // typo in the name
describe(): string { return "square"; } // override keyword missing
}
error TS4113: This member cannot have an 'override' modifier because it is not declared in the base class 'Shape'.
error TS4114: This member must have an 'override' modifier because it overrides a member in the base class 'Shape'.
The first error comes from the keyword itself, and the second one comes from the noImplicitOverride option. Without that option, override is optional, and a method that replaces a parent method by accident compiles without a warning. With it, every override is visible in the code, and when we add a method to a parent class that clashes with a subclass method, the compiler reports the clash at once. We turn the option on in tsconfig.json.
{
"compilerOptions": {
"strict": true,
"noImplicitOverride": true
}
}
The option is not part of strict, so we must set it explicitly. The compiler removes the override keyword from the compiled JavaScript, so it has no runtime cost.
3. Calling the Parent Method With super
Inside an overriding method, super.methodName() runs the parent’s version, and we use it when the subclass adds to the parent behavior instead of replacing it. For example, an admin greeting is the normal user greeting plus the admin’s role. The same keyword, called as super(…), runs the parent constructor.
class User {
name: string;
constructor(name: string) { this.name = name; }
greet(): string { return "Hi, I am " + this.name; }
}
class Admin extends User {
role: string;
// 1. super(...) runs the parent constructor first
constructor(name: string, role: string) {
super(name);
this.role = role;
}
// 2. super.greet() runs the parent method
override greet(): string {
return super.greet() + " (" + this.role + ")";
}
}
const lokesh = new Admin("Lokesh", "owner");
const text = lokesh.greet(); // text = "Hi, I am Lokesh (owner)"
A subclass constructor must call super() before it touches this, as in Java. Writing this.role = role before the call fails with error TS17009: ‘super’ must be called before accessing ‘this’ in the constructor of a derived class. A subclass that needs no extra setup can leave out the constructor, and it inherits the parent constructor.
4. Signature and Visibility Rules
An object of a subclass must work wherever the parent type is expected. The compiler checks this subtype rule on every override, so the new method must accept every call that the parent method accepts and return a value the caller can use.
| Change in the override | Allowed | Reason |
|---|---|---|
| Same parameters and return type | Yes | Same contract |
| Fewer parameters | Yes | Extra arguments are ignored at runtime |
| An extra optional parameter | Yes | Old calls still work |
| An extra required parameter | No (TS2416) | Old calls would pass undefined |
| Narrower return type, for example a literal | Yes | Callers still get the expected type |
| Different return type, for example string for number | No (TS2416) | Callers would get the wrong type |
| protected in the parent, public in the subclass | Yes | Visibility may grow |
| public in the parent, private or protected in the subclass | No (TS2415) | Visibility may not shrink |
| Any override of a private method | No (TS2415) | Private methods are not inherited for overriding |
The allowed cases compile and run, and the optional parameter keeps old calls such as raj.greet() working.
class User {
name: string;
constructor(name: string) { this.name = name; }
greet(): string { return "Hi, I am " + this.name; }
protected label(): string { return "user"; }
}
class Admin extends User {
// 1. An extra parameter is allowed when it is optional
override greet(prefix?: string): string {
return (prefix ?? "") + super.greet();
}
// 2. A protected method may become public
override label(): string { return "admin"; }
}
const raj = new Admin("Raj");
const t1 = raj.greet(); // t1 = "Hi, I am Raj"
const t2 = raj.greet("Welcome. "); // t2 = "Welcome. Hi, I am Raj"
const t3 = raj.label(); // t3 = "admin"
Two of the rejected cases fail with TS2416, namely a Square whose area() returns a string and a Rectangle whose describe() adds a required parameter.
error TS2416: Property 'area' in type 'Square' is not assignable to the same property in base type 'Shape'.
Type '() => string' is not assignable to type '() => number'.
Type 'string' is not assignable to type 'number'.
error TS2416: Property 'describe' in type 'Rectangle' is not assignable to the same property in base type 'Shape'.
Type '(prefix: string) => string' is not assignable to type '() => string'.
Target signature provides too few arguments. Expected 1 or more, but got 0.
The compiler does not catch one case, an override that accepts a narrower parameter type. If the parent has feed(food: string | number), an override feed(food: string) compiles, even with strict, because TypeScript checks method parameters bivariantly for compatibility with existing JavaScript code. A caller holding the object as the parent type can still pass a number, which the override does not expect. We keep parameter types the same as in the parent.
Overriding is also different from function overloading, which declares several signatures for one method in the same class, whereas overriding replaces a method of the parent class.
5. Abstract Methods That Subclasses Must Override
An abstract method is declared in an abstract class with a signature and no body. Every non-abstract subclass must override it, so the compiler guarantees that the method exists on every object we can create. The parent class can still call it, as describe() does here. For example, a report base class formats every report, but only each subclass knows how to load its own data.
abstract class Shape {
abstract area(): number; // no body, subclasses must provide it
describe(): string { return "area " + this.area(); }
}
class Square extends Shape {
side: number;
constructor(side: number) {
super();
this.side = side;
}
override area(): number { return this.side * this.side; }
}
const text = new Square(5).describe(); // text = "area 25"
A subclass that forgets the method fails with error TS2515: Non-abstract class ‘Circle’ does not implement inherited abstract member area from class ‘Shape’. For abstract methods, noImplicitOverride does not require the override keyword, since nothing is being replaced, but the keyword is allowed and we use it for consistency. An abstract class with abstract and regular methods is the TypeScript replacement for Java’s interface default methods.
6. Example Project on GitHub
The method-overriding-example project runs every snippet from the sections. Its tsconfig.json turns on noImplicitOverride in addition to strict, so removing any override keyword from the code makes the build fail. The versions are pinned to TypeScript 7.0.2, and any Node.js release from 22 onward runs it.
npm install
npm start
Its console output repeats each value from the snippet comments.
7. Conclusion
TypeScript method overriding lets a subclass replace a parent method, and the runtime always picks the version that belongs to the object’s class. The override keyword together with noImplicitOverride turns overriding mistakes, such as misspelled names and accidental overrides, into compile errors, so we enable both in every project. Inside an override, super calls the parent version. The compiler checks that each override keeps the parent’s contract, with one gap for narrower parameter types, and abstract methods make overriding mandatory.
8. References
The release notes introduced the override keyword, and the handbook explains the subtype rule that every override must follow.
- TypeScript 4.3 release notes: override and noImplicitOverride
- TSConfig reference: noImplicitOverride
- TypeScript Handbook: Classes, overriding methods
- TypeScript Handbook: abstract classes and members
- MDN: super
Happy Learning !!