Back to Blog
TypeScriptoverrideClassesInheritance

TypeScript override Keyword Guide

Master the TypeScript override keyword for explicit method overriding and safer inheritance patterns.

B
Bootspring Team
Engineering
November 27, 2019
6 min read

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 process

With 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.

Share this article

Help spread the word about Bootspring