The override keyword explicitly marks methods that override base class methods. Here's how to use it safely.
Basic Usage#
1class Animal {
2 move() {
3 console.log('Moving...');
4 }
5
6 speak() {
7 console.log('Some sound');
8 }
9}
10
11class Dog extends Animal {
12 // Explicitly override base method
13 override move() {
14 console.log('Running on four legs');
15 }
16
17 override speak() {
18 console.log('Woof!');
19 }
20}
21
22// Error: Method does not override anything
23class Cat extends Animal {
24 override meow() { // Error!
25 console.log('Meow');
26 }
27}Enable noImplicitOverride#
1// tsconfig.json
2{
3 "compilerOptions": {
4 "noImplicitOverride": true
5 }
6}1// With noImplicitOverride enabled
2class Animal {
3 move() {
4 console.log('Moving');
5 }
6}
7
8class Bird extends Animal {
9 // Error: Must use override keyword
10 move() {
11 console.log('Flying');
12 }
13
14 // Correct
15 override move() {
16 console.log('Flying');
17 }
18}Catching Refactoring Errors#
1// Before refactoring
2class BaseService {
3 fetchData() {
4 return [];
5 }
6}
7
8class UserService extends BaseService {
9 override fetchData() {
10 return ['user1', 'user2'];
11 }
12}
13
14// After refactoring - base method renamed
15class BaseService {
16 getData() { // Renamed
17 return [];
18 }
19}
20
21// TypeScript catches the error
22class UserService extends BaseService {
23 override fetchData() { // Error: Method does not override
24 return ['user1', 'user2'];
25 }
26}Abstract Methods#
1abstract class Shape {
2 abstract getArea(): number;
3 abstract getPerimeter(): number;
4
5 describe() {
6 return `Area: ${this.getArea()}, Perimeter: ${this.getPerimeter()}`;
7 }
8}
9
10class Rectangle extends Shape {
11 constructor(
12 private width: number,
13 private height: number
14 ) {
15 super();
16 }
17
18 // Must implement abstract methods
19 override getArea() {
20 return this.width * this.height;
21 }
22
23 override getPerimeter() {
24 return 2 * (this.width + this.height);
25 }
26
27 // Can also override non-abstract methods
28 override describe() {
29 return `Rectangle: ${super.describe()}`;
30 }
31}Protected Methods#
1class Component {
2 protected render(): string {
3 return '<div></div>';
4 }
5
6 protected onMount(): void {
7 console.log('Mounted');
8 }
9
10 mount() {
11 const html = this.render();
12 document.body.innerHTML = html;
13 this.onMount();
14 }
15}
16
17class Button extends Component {
18 constructor(private label: string) {
19 super();
20 }
21
22 protected override render() {
23 return `<button>${this.label}</button>`;
24 }
25
26 protected override onMount() {
27 super.onMount();
28 console.log('Button mounted');
29 }
30}Accessors#
1class User {
2 private _name: string = '';
3
4 get name() {
5 return this._name;
6 }
7
8 set name(value: string) {
9 this._name = value;
10 }
11}
12
13class Admin extends User {
14 private _role: string = 'admin';
15
16 // Override getter
17 override get name() {
18 return `[Admin] ${super.name}`;
19 }
20
21 // Override setter
22 override set name(value: string) {
23 // Add validation
24 if (value.length < 2) {
25 throw new Error('Name too short');
26 }
27 super.name = value;
28 }
29}Constructor Patterns#
1class BaseEntity {
2 id: string;
3 createdAt: Date;
4
5 constructor() {
6 this.id = generateId();
7 this.createdAt = new Date();
8 }
9
10 protected initialize(): void {
11 // Base initialization
12 }
13}
14
15class User extends BaseEntity {
16 email: string;
17
18 constructor(email: string) {
19 super();
20 this.email = email;
21 this.initialize();
22 }
23
24 protected override initialize() {
25 super.initialize();
26 // User-specific initialization
27 console.log(`User ${this.email} created`);
28 }
29}Multiple Inheritance Levels#
1class Base {
2 process(): void {
3 console.log('Base process');
4 }
5}
6
7class Middle extends Base {
8 override process(): void {
9 console.log('Middle process');
10 super.process();
11 }
12}
13
14class Derived extends Middle {
15 override process(): void {
16 console.log('Derived process');
17 super.process();
18 }
19}
20
21const d = new Derived();
22d.process();
23// Derived process
24// Middle process
25// Base processWith Mixins#
1type Constructor<T = {}> = new (...args: any[]) => T;
2
3// Mixin that adds logging
4function Loggable<TBase extends Constructor>(Base: TBase) {
5 return class extends Base {
6 log(message: string) {
7 console.log(`[${this.constructor.name}] ${message}`);
8 }
9 };
10}
11
12class BaseService {
13 fetch() {
14 return 'data';
15 }
16}
17
18class LoggableService extends Loggable(BaseService) {
19 // Must use override when parent has the method
20 override fetch() {
21 this.log('Fetching data');
22 return super.fetch();
23 }
24}Generic Classes#
1class Repository<T> {
2 protected items: T[] = [];
3
4 add(item: T): void {
5 this.items.push(item);
6 }
7
8 getAll(): T[] {
9 return [...this.items];
10 }
11
12 protected validate(item: T): boolean {
13 return true;
14 }
15}
16
17class UserRepository extends Repository<User> {
18 override add(user: User): void {
19 if (!this.validate(user)) {
20 throw new Error('Invalid user');
21 }
22 super.add(user);
23 }
24
25 protected override validate(user: User): boolean {
26 return user.email.includes('@');
27 }
28
29 // New method - no override needed
30 findByEmail(email: string): User | undefined {
31 return this.items.find((u) => u.email === email);
32 }
33}Event Handlers#
1class EventEmitter {
2 protected handlers: Map<string, Function[]> = new Map();
3
4 on(event: string, handler: Function): void {
5 const handlers = this.handlers.get(event) || [];
6 handlers.push(handler);
7 this.handlers.set(event, handlers);
8 }
9
10 protected emit(event: string, data?: any): void {
11 const handlers = this.handlers.get(event) || [];
12 handlers.forEach((h) => h(data));
13 }
14}
15
16class TypedEmitter<Events extends Record<string, any>> extends EventEmitter {
17 override on<K extends keyof Events>(
18 event: K,
19 handler: (data: Events[K]) => void
20 ): void {
21 super.on(event as string, handler);
22 }
23
24 protected override emit<K extends keyof Events>(
25 event: K,
26 data?: Events[K]
27 ): void {
28 super.emit(event as string, data);
29 }
30}
31
32// Usage
33interface AppEvents {
34 login: { userId: string };
35 logout: void;
36}
37
38class AppEmitter extends TypedEmitter<AppEvents> {
39 login(userId: string) {
40 this.emit('login', { userId });
41 }
42}Testing with Override#
1class HttpClient {
2 async get(url: string): Promise<any> {
3 const response = await fetch(url);
4 return response.json();
5 }
6}
7
8// Mock for testing
9class MockHttpClient extends HttpClient {
10 private responses: Map<string, any> = new Map();
11
12 setResponse(url: string, data: any) {
13 this.responses.set(url, data);
14 }
15
16 override async get(url: string): Promise<any> {
17 if (this.responses.has(url)) {
18 return this.responses.get(url);
19 }
20 throw new Error(`No mock for ${url}`);
21 }
22}
23
24// Test
25const mockClient = new MockHttpClient();
26mockClient.setResponse('/api/users', [{ id: 1 }]);
27const users = await mockClient.get('/api/users');Best Practices#
Usage:
✓ Enable noImplicitOverride
✓ Always use override when intended
✓ Call super when needed
✓ Match signature exactly
Benefits:
✓ Catches refactoring errors
✓ Documents intent clearly
✓ Prevents accidental overrides
✓ IDE support and autocomplete
Patterns:
✓ Template method pattern
✓ Hook methods
✓ Testing with mocks
✓ Framework extensions
Avoid:
✗ Overriding without super when needed
✗ Changing method semantics
✗ Deep inheritance hierarchies
✗ Overriding to do nothing
Conclusion#
The override keyword makes method overriding explicit and catches errors when base methods are renamed or removed. Enable noImplicitOverride in your tsconfig for the best experience. Use it consistently when extending classes to maintain a clear inheritance hierarchy and prevent subtle bugs during refactoring.