Prerequisites
- Classes in TypeScript basics ๐
- Constructors and methods ๐๏ธ
- Access modifiers knowledge ๐
What you'll learn
- Understand inheritance fundamentals ๐ฏ
- Create class hierarchies effectively ๐๏ธ
- Use extends keyword properly โจ
- Apply inheritance in real projects ๐
๐ฏ Introduction
Welcome to the exciting world of TypeScript inheritance! ๐ In this guide, weโll explore one of the most powerful features of object-oriented programming: the ability to create new classes based on existing ones.
Youโll discover how inheritance can help you write more reusable, maintainable code by building class hierarchies. Whether youโre building game characters ๐ฎ, UI components ๐ผ๏ธ, or business entities ๐ผ, understanding inheritance is essential for writing scalable TypeScript applications.
By the end of this tutorial, youโll feel confident creating class hierarchies that follow best practices! Letโs dive in! ๐โโ๏ธ
๐ Understanding Inheritance
๐ค What is Inheritance?
Inheritance is like a family tree ๐ณ. Think of it as a way for classes to share characteristics and behaviors, just like how children inherit traits from their parents.
In TypeScript terms, inheritance allows you to create a new class (child/derived class) that inherits properties and methods from an existing class (parent/base class). This means you can:
- โจ Reuse existing code without duplication
- ๐ Build upon proven functionality
- ๐ก๏ธ Maintain consistent interfaces
- ๐ง Extend behavior without modifying original code
๐ก Why Use Inheritance?
Hereโs why developers love inheritance:
- Code Reusability โป๏ธ: Write once, inherit everywhere
- Maintainability ๐ง: Changes in base class affect all children
- Logical Hierarchy ๐๏ธ: Models real-world relationships
- Polymorphism ๐ญ: Treat different objects uniformly
Real-world example: Imagine building a zoo management system ๐ฆ. You can create an Animal
base class and inherit specific animals like Lion
, Elephant
, and Penguin
.
๐ง Basic Syntax and Usage
๐ Simple Example
Letโs start with a friendly example:
// ๐จโ๐ฉโ๐งโ๐ฆ Base class - the parent
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// ๐ Basic greeting method
greet(): string {
return `Hello, I'm ${this.name}! ๐`;
}
// ๐ Birthday celebration
haveBirthday(): void {
this.age++;
console.log(`๐ ${this.name} is now ${this.age} years old!`);
}
}
// ๐จโ๐ Child class - extends the parent
class Student extends Person {
studentId: string;
major: string;
constructor(name: string, age: number, studentId: string, major: string) {
super(name, age); // ๐ Call parent constructor
this.studentId = studentId;
this.major = major;
}
// ๐ Student-specific method
study(): string {
return `${this.name} is studying ${this.major}! ๐`;
}
// ๐ Get student info
getStudentInfo(): string {
return `Student ID: ${this.studentId}, Major: ${this.major} ๐`;
}
}
๐ก Explanation: The Student
class inherits all properties and methods from Person
and adds its own specific features!
๐ฏ Using Inherited Classes
Hereโs how to use our inherited classes:
// ๐๏ธ Create instances
const person = new Person("Alice", 30);
const student = new Student("Bob", 20, "S12345", "Computer Science");
// ๐ค Regular person
console.log(person.greet()); // "Hello, I'm Alice! ๐"
person.haveBirthday(); // "๐ Alice is now 31 years old!"
// ๐จโ๐ Student (has all Person methods + Student methods)
console.log(student.greet()); // "Hello, I'm Bob! ๐" (inherited!)
console.log(student.study()); // "Bob is studying Computer Science! ๐"
console.log(student.getStudentInfo()); // "Student ID: S12345, Major: Computer Science ๐"
// ๐ Student can also have birthdays!
student.haveBirthday(); // "๐ Bob is now 21 years old!"
๐ก Practical Examples
๐ฎ Example 1: Game Character System
Letโs build a fun game character hierarchy:
// ๐ฐ Base character class
class GameCharacter {
name: string;
health: number;
level: number;
experience: number;
constructor(name: string) {
this.name = name;
this.health = 100;
this.level = 1;
this.experience = 0;
}
// โ๏ธ Basic attack method
attack(): number {
const damage = Math.floor(Math.random() * 20) + 5;
console.log(`${this.name} attacks for ${damage} damage! โ๏ธ`);
return damage;
}
// ๐ก๏ธ Take damage
takeDamage(damage: number): void {
this.health = Math.max(0, this.health - damage);
console.log(`${this.name} takes ${damage} damage! Health: ${this.health} โค๏ธ`);
if (this.health === 0) {
console.log(`๐ ${this.name} has been defeated!`);
}
}
// โญ Gain experience
gainExperience(xp: number): void {
this.experience += xp;
if (this.experience >= this.level * 100) {
this.levelUp();
}
}
// ๐ Level up
private levelUp(): void {
this.level++;
this.health = 100; // ๐ Full heal on level up
console.log(`๐ ${this.name} leveled up to ${this.level}!`);
}
}
// ๐งโโ๏ธ Wizard class - magic focused
class Wizard extends GameCharacter {
mana: number;
spellPower: number;
constructor(name: string) {
super(name); // ๐ Call parent constructor
this.mana = 50;
this.spellPower = 15;
}
// โจ Cast magic spell
castSpell(): number {
if (this.mana < 10) {
console.log(`${this.name} doesn't have enough mana! ๐`);
return 0;
}
this.mana -= 10;
const damage = this.spellPower + Math.floor(Math.random() * 10);
console.log(`${this.name} casts a spell for ${damage} magical damage! โจ`);
return damage;
}
// ๐งโโ๏ธ Meditate to restore mana
meditate(): void {
this.mana = Math.min(100, this.mana + 20);
console.log(`${this.name} meditates and restores mana. Current: ${this.mana} ๐งโโ๏ธ`);
}
}
// โ๏ธ Warrior class - combat focused
class Warrior extends GameCharacter {
armor: number;
strength: number;
constructor(name: string) {
super(name);
this.armor = 25;
this.strength = 20;
}
// ๐ก๏ธ Defensive stance
defendersStance(): void {
console.log(`${this.name} takes a defensive stance! ๐ก๏ธ`);
this.armor += 10;
// ๐ Remove bonus after some time (simplified)
setTimeout(() => {
this.armor -= 10;
console.log(`${this.name}'s defensive stance ends.`);
}, 3000);
}
// ๐ฅ Power attack
powerAttack(): number {
const damage = this.strength + Math.floor(Math.random() * 15) + 10;
console.log(`${this.name} performs a powerful attack for ${damage} damage! ๐ฅ`);
return damage;
}
// ๐ก๏ธ Override takeDamage to account for armor
takeDamage(damage: number): void {
const reducedDamage = Math.max(1, damage - this.armor);
super.takeDamage(reducedDamage);
console.log(`๐ก๏ธ Armor reduced damage from ${damage} to ${reducedDamage}`);
}
}
// ๐ฎ Let's play!
const gandalf = new Wizard("Gandalf");
const conan = new Warrior("Conan");
gandalf.castSpell(); // Magical attack!
conan.powerAttack(); // Physical attack!
conan.defendersStance(); // Defensive move!
๐ฏ Try it yourself: Add a Rogue
class with stealth and backstab abilities!
๐ช Example 2: E-commerce Product System
Letโs create a product hierarchy for an online store:
// ๐๏ธ Base product class
class Product {
id: string;
name: string;
price: number;
category: string;
inStock: boolean;
constructor(id: string, name: string, price: number, category: string) {
this.id = id;
this.name = name;
this.price = price;
this.category = category;
this.inStock = true;
}
// ๐ฐ Get formatted price
getFormattedPrice(): string {
return `$${this.price.toFixed(2)} ๐ฐ`;
}
// ๐ฆ Check availability
checkAvailability(): boolean {
return this.inStock;
}
// ๐ท๏ธ Apply discount
applyDiscount(percentage: number): void {
if (percentage > 0 && percentage <= 100) {
this.price = this.price * (1 - percentage / 100);
console.log(`๐ ${percentage}% discount applied! New price: ${this.getFormattedPrice()}`);
}
}
// ๐ Get product summary
getSummary(): string {
return `${this.name} - ${this.getFormattedPrice()} [${this.category}] ๐ฆ`;
}
}
// ๐ Clothing product
class Clothing extends Product {
size: string;
color: string;
material: string;
constructor(id: string, name: string, price: number, size: string, color: string, material: string) {
super(id, name, price, "Clothing");
this.size = size;
this.color = color;
this.material = material;
}
// ๐ Get clothing details
getClothingDetails(): string {
return `Size: ${this.size}, Color: ${this.color}, Material: ${this.material} ๐`;
}
// ๐งบ Washing instructions
getWashingInstructions(): string {
const instructions = {
cotton: "Machine wash cold, tumble dry low ๐งบ",
silk: "Dry clean only โจ",
wool: "Hand wash cold, lay flat to dry ๐",
polyester: "Machine wash warm, tumble dry medium ๐"
};
return instructions[this.material.toLowerCase()] || "Follow care label instructions ๐";
}
}
// ๐ฑ Electronics product
class Electronics extends Product {
brand: string;
warranty: number; // in months
powerRating: string;
constructor(id: string, name: string, price: number, brand: string, warranty: number, powerRating: string) {
super(id, name, price, "Electronics");
this.brand = brand;
this.warranty = warranty;
this.powerRating = powerRating;
}
// ๐ง Get tech specs
getTechSpecs(): string {
return `Brand: ${this.brand}, Warranty: ${this.warranty} months, Power: ${this.powerRating} โก`;
}
// ๐ก๏ธ Extended warranty
extendWarranty(additionalMonths: number): void {
this.warranty += additionalMonths;
console.log(`๐ก๏ธ Warranty extended! New warranty: ${this.warranty} months`);
}
// ๐ Power efficiency rating
getPowerEfficiency(): string {
const rating = this.powerRating.toLowerCase();
if (rating.includes("low") || rating.includes("5w")) return "Excellent ๐";
if (rating.includes("medium") || rating.includes("15w")) return "Good ๐";
return "Standard โก";
}
}
// ๐ Let's go shopping!
const tshirt = new Clothing("C001", "Cool TypeScript T-Shirt", 29.99, "L", "Blue", "Cotton");
const laptop = new Electronics("E001", "DevBook Pro", 1299.99, "TechCorp", 24, "65W");
console.log(tshirt.getSummary()); // Product info
console.log(tshirt.getClothingDetails()); // Clothing-specific info
console.log(tshirt.getWashingInstructions()); // Care instructions
console.log(laptop.getSummary()); // Product info
console.log(laptop.getTechSpecs()); // Electronics-specific info
laptop.extendWarranty(12); // Extend warranty
// ๐ Apply discounts
tshirt.applyDiscount(20); // 20% off clothing
laptop.applyDiscount(10); // 10% off electronics
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: Protected Members
When building inheritance hierarchies, you often want to share data between parent and child classes, but not expose it publicly:
// ๐ฆ Bank account hierarchy
class BankAccount {
protected accountNumber: string; // ๐ Protected - accessible by children
protected balance: number;
private pin: string; // ๐ซ Private - only this class
constructor(accountNumber: string, initialBalance: number, pin: string) {
this.accountNumber = accountNumber;
this.balance = initialBalance;
this.pin = pin;
}
// ๐ Public method to check balance
public getBalance(): number {
return this.balance;
}
// ๐ Protected method - children can use this
protected validateTransaction(amount: number): boolean {
return amount > 0 && amount <= this.balance;
}
}
// ๐ณ Savings account with interest
class SavingsAccount extends BankAccount {
private interestRate: number;
constructor(accountNumber: string, initialBalance: number, pin: string, interestRate: number) {
super(accountNumber, initialBalance, pin);
this.interestRate = interestRate;
}
// ๐ฐ Add interest - uses protected balance
addInterest(): void {
const interest = this.balance * (this.interestRate / 100);
this.balance += interest; // โ
Can access protected member
console.log(`๐ฐ Interest added: $${interest.toFixed(2)}. New balance: $${this.balance.toFixed(2)}`);
}
// ๐ฆ Withdraw with validation - uses protected method
withdraw(amount: number): boolean {
if (this.validateTransaction(amount)) { // โ
Can use protected method
this.balance -= amount;
console.log(`๐ธ Withdrew $${amount}. New balance: $${this.balance.toFixed(2)}`);
return true;
}
console.log("โ Invalid transaction");
return false;
}
}
๐๏ธ Advanced Topic 2: Abstract Base Classes
Sometimes you want to create a base class that should never be instantiated directly:
// ๐จ Abstract shape class - blueprint only
abstract class Shape {
protected x: number;
protected y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
// ๐ Concrete method - all shapes can move
move(deltaX: number, deltaY: number): void {
this.x += deltaX;
this.y += deltaY;
console.log(`๐ Moved to (${this.x}, ${this.y})`);
}
// ๐ฏ Abstract methods - must be implemented by children
abstract getArea(): number;
abstract getPerimeter(): number;
abstract draw(): void;
}
// ๐ต Circle implementation
class Circle extends Shape {
private radius: number;
constructor(x: number, y: number, radius: number) {
super(x, y);
this.radius = radius;
}
getArea(): number {
return Math.PI * this.radius * this.radius;
}
getPerimeter(): number {
return 2 * Math.PI * this.radius;
}
draw(): void {
console.log(`๐ต Drawing circle at (${this.x}, ${this.y}) with radius ${this.radius}`);
}
}
// ๐ท Rectangle implementation
class Rectangle extends Shape {
private width: number;
private height: number;
constructor(x: number, y: number, width: number, height: number) {
super(x, y);
this.width = width;
this.height = height;
}
getArea(): number {
return this.width * this.height;
}
getPerimeter(): number {
return 2 * (this.width + this.height);
}
draw(): void {
console.log(`๐ท Drawing rectangle at (${this.x}, ${this.y}) - ${this.width}x${this.height}`);
}
}
// ๐จ Using our shapes
const circle = new Circle(10, 20, 5);
const rectangle = new Rectangle(0, 0, 10, 8);
circle.draw(); // ๐ต Drawing circle...
console.log(`Circle area: ${circle.getArea().toFixed(2)} ๐`);
rectangle.move(5, 5); // ๐ Moved to (5, 5)
rectangle.draw(); // ๐ท Drawing rectangle...
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Forgetting to Call super()
// โ Wrong way - missing super() call!
class BadStudent extends Person {
studentId: string;
constructor(name: string, age: number, studentId: string) {
// Missing super(name, age) - This will cause an error!
this.studentId = studentId;
}
}
// โ
Correct way - always call super()!
class GoodStudent extends Person {
studentId: string;
constructor(name: string, age: number, studentId: string) {
super(name, age); // โ
Call parent constructor first!
this.studentId = studentId;
}
}
๐คฏ Pitfall 2: Overusing Inheritance
// โ Dangerous - too many inheritance levels!
class Animal { }
class Mammal extends Animal { }
class Carnivore extends Mammal { }
class Feline extends Carnivore { }
class BigCat extends Feline { }
class Lion extends BigCat { } // ๐ต Too deep!
// โ
Better - use composition when appropriate!
interface Carnivorous {
hunt(): void;
}
interface Feline {
purr(): void;
retractClaws(): void;
}
class Lion extends Animal implements Carnivorous, Feline {
hunt(): void {
console.log("๐ฆ Lion hunts for prey!");
}
purr(): void {
console.log("๐บ Lion purrs contentedly");
}
retractClaws(): void {
console.log("๐พ Lion retracts claws");
}
}
๐ Pitfall 3: Not Understanding Method Resolution
class Parent {
greet(): string {
return "Hello from Parent! ๐";
}
}
class Child extends Parent {
greet(): string {
return "Hello from Child! ๐ถ";
}
// โ Common confusion - which greet() gets called?
confusingMethod(): void {
console.log(this.greet()); // Will call Child's greet(), not Parent's!
}
// โ
Correct - explicitly call parent method when needed
clarMethod(): void {
console.log(super.greet()); // Calls Parent's greet()
console.log(this.greet()); // Calls Child's greet()
}
}
๐ ๏ธ Best Practices
- ๐ฏ Favor Composition Over Inheritance: Donโt inherit just to reuse code
- ๐ Keep Hierarchies Shallow: Avoid deep inheritance chains (max 3-4 levels)
- ๐ก๏ธ Use Protected Wisely: Share data with children, not the world
- โจ Call super() First: Always call parent constructor before your code
- ๐จ Consider Interfaces: Sometimes interface implementation is better than inheritance
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Media Library System
Create a type-safe media library with inheritance:
๐ Requirements:
- โ
Base
MediaItem
class with title, duration, rating - ๐ฌ
Movie
class with director, genre, cast - ๐ต
Song
class with artist, album, genre - ๐
Audiobook
class with author, narrator, chapters - ๐ฎ Each item needs appropriate emoji and methods!
๐ Bonus Points:
- Add a
Playlist
class that can contain any media type - Implement rating system and reviews
- Add search and filter functionality
๐ก Solution
๐ Click to see solution
// ๐ญ Base media item class
abstract class MediaItem {
title: string;
duration: number; // in minutes
rating: number; // 1-5 stars
dateAdded: Date;
constructor(title: string, duration: number) {
this.title = title;
this.duration = duration;
this.rating = 0;
this.dateAdded = new Date();
}
// โญ Set rating
setRating(rating: number): void {
if (rating >= 1 && rating <= 5) {
this.rating = rating;
console.log(`โญ Rated "${this.title}": ${rating}/5 stars`);
}
}
// ๐ Get formatted duration
getFormattedDuration(): string {
const hours = Math.floor(this.duration / 60);
const minutes = this.duration % 60;
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
}
// ๐ Abstract methods
abstract getInfo(): string;
abstract getCategory(): string;
abstract play(): void;
}
// ๐ฌ Movie class
class Movie extends MediaItem {
director: string;
genre: string;
cast: string[];
year: number;
constructor(title: string, duration: number, director: string, genre: string, cast: string[], year: number) {
super(title, duration);
this.director = director;
this.genre = genre;
this.cast = cast;
this.year = year;
}
getCategory(): string {
return "Movie ๐ฌ";
}
getInfo(): string {
return `๐ฌ ${this.title} (${this.year})
๐ฝ๏ธ Director: ${this.director}
๐ญ Genre: ${this.genre}
โฑ๏ธ Duration: ${this.getFormattedDuration()}
๐ Cast: ${this.cast.join(", ")}
โญ Rating: ${this.rating}/5`;
}
play(): void {
console.log(`๐ฌ Now playing: "${this.title}" directed by ${this.director}`);
}
// ๐ Get Oscar potential
getOscarPotential(): string {
if (this.rating >= 4.5) return "Oscar worthy! ๐";
if (this.rating >= 4.0) return "Strong contender ๐ฅ";
if (this.rating >= 3.5) return "Decent film ๐";
return "Popcorn movie ๐ฟ";
}
}
// ๐ต Song class
class Song extends MediaItem {
artist: string;
album: string;
genre: string;
year: number;
constructor(title: string, duration: number, artist: string, album: string, genre: string, year: number) {
super(title, duration);
this.artist = artist;
this.album = album;
this.genre = genre;
this.year = year;
}
getCategory(): string {
return "Song ๐ต";
}
getInfo(): string {
return `๐ต ${this.title}
๐ค Artist: ${this.artist}
๐ฟ Album: ${this.album}
๐ผ Genre: ${this.genre}
๐
Year: ${this.year}
โฑ๏ธ Duration: ${this.getFormattedDuration()}
โญ Rating: ${this.rating}/5`;
}
play(): void {
console.log(`๐ต Now playing: "${this.title}" by ${this.artist}`);
}
// ๐ฅ Check if it's a hit
isHit(): boolean {
return this.rating >= 4.0 && this.duration <= 4; // Great rating, not too long
}
}
// ๐ Audiobook class
class Audiobook extends MediaItem {
author: string;
narrator: string;
chapters: number;
genre: string;
constructor(title: string, duration: number, author: string, narrator: string, chapters: number, genre: string) {
super(title, duration);
this.author = author;
this.narrator = narrator;
this.chapters = chapters;
this.genre = genre;
}
getCategory(): string {
return "Audiobook ๐";
}
getInfo(): string {
return `๐ ${this.title}
โ๏ธ Author: ${this.author}
๐๏ธ Narrator: ${this.narrator}
๐ Chapters: ${this.chapters}
๐ท๏ธ Genre: ${this.genre}
โฑ๏ธ Duration: ${this.getFormattedDuration()}
โญ Rating: ${this.rating}/5`;
}
play(): void {
console.log(`๐ Now playing: "${this.title}" by ${this.author}, narrated by ${this.narrator}`);
}
// ๐ Average chapter length
getAverageChapterLength(): string {
const avgMinutes = Math.round(this.duration / this.chapters);
return `${avgMinutes} minutes per chapter`;
}
}
// ๐๏ธ Media library management
class MediaLibrary {
private items: MediaItem[] = [];
addItem(item: MediaItem): void {
this.items.push(item);
console.log(`โ Added ${item.getCategory()}: "${item.title}"`);
}
searchByTitle(title: string): MediaItem[] {
return this.items.filter(item =>
item.title.toLowerCase().includes(title.toLowerCase())
);
}
getByCategory(category: string): MediaItem[] {
return this.items.filter(item =>
item.getCategory().toLowerCase().includes(category.toLowerCase())
);
}
getTopRated(count: number = 5): MediaItem[] {
return this.items
.filter(item => item.rating > 0)
.sort((a, b) => b.rating - a.rating)
.slice(0, count);
}
getTotalDuration(): string {
const totalMinutes = this.items.reduce((sum, item) => sum + item.duration, 0);
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
return `${hours}h ${minutes}m of content! ๐`;
}
showLibrary(): void {
console.log("๐๏ธ Media Library Contents:");
this.items.forEach((item, index) => {
console.log(`${index + 1}. ${item.getCategory()}: "${item.title}" โญ${item.rating}/5`);
});
console.log(`\n๐ Total: ${this.items.length} items, ${this.getTotalDuration()}`);
}
}
// ๐ฎ Let's build our library!
const library = new MediaLibrary();
// Add some content
const movie = new Movie("The TypeScript Chronicles", 142, "Code Director", "Sci-Fi", ["Dev Hero", "Bug Villain"], 2023);
const song = new Song("Async Await Blues", 3, "The Promises", "Callback Hell", "Tech Rock", 2022);
const audiobook = new Audiobook("Clean Code", 720, "Robert Martin", "Tech Narrator", 24, "Programming");
movie.setRating(5);
song.setRating(4);
audiobook.setRating(5);
library.addItem(movie);
library.addItem(song);
library.addItem(audiobook);
// Explore our library
library.showLibrary();
console.log("\n๐ Top rated content:");
library.getTopRated(3).forEach(item => console.log(item.getInfo()));
๐ Key Takeaways
Youโve learned so much about inheritance! Hereโs what you can now do:
- โ Create class hierarchies with confidence ๐ช
- โ Use extends keyword properly ๐๏ธ
- โ Call parent constructors with super() ๐
- โ Share code effectively between related classes โป๏ธ
- โ Apply inheritance in real-world scenarios ๐ฏ
Remember: Inheritance is a powerful tool, but use it wisely! Sometimes composition is better than inheritance. ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered TypeScript inheritance!
Hereโs what to do next:
- ๐ป Practice with the game character exercise above
- ๐๏ธ Build your own class hierarchy for a domain youโre interested in
- ๐ Move on to our next tutorial: Super Keyword: Calling Parent Class Members
- ๐ Experiment with abstract classes and protected members!
Remember: Every TypeScript expert started with classes and inheritance. Keep building, keep learning, and most importantly, have fun! ๐
Happy coding! ๐๐โจ