TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
20: Extending Interfaces
I've seen this happen in almost every mid-sized project I've joined. A developer needs a specialized version of an existing object, so they copy and paste the original interface, add one or two new fields, and call it a day. It feels efficient in the moment, but it's a ticking time bomb for your type safety.
The Maintenance Trap
// The base user
interface User {
id: string;
email: string;
displayName: string;
}
// The admin user - looks fine, right?
interface AdminUser {
id: string;
email: string;
displayName: string;
adminLevel: number;
}
function sendWelcomeEmail(user: User) {
console.log(`Sending email to ${user.email}...`);
}
const admin: AdminUser = {
id: 'a1',
email: 'admin@company.com',
displayName: 'Super User',
adminLevel: 1
};
sendWelcomeEmail(admin); // This works because of structural typing
Everything looks great until the requirements change. Let's say your PM decides all users now need a timezone property to handle scheduled notifications. You update the User interface, but you forget to update AdminUser because it's defined in a different file.
interface User {
id: string;
email: string;
displayName: string;
timezone: string; // Added this
}
interface AdminUser {
id: string;
email: string;
displayName: string;
adminLevel: number;
// Whoops, forgot timezone here!
}
function sendWelcomeEmail(user: User) {
console.log(`Sending email to ${user.email} in ${user.timezone}...`);
}
const admin: AdminUser = {
id: 'a1',
email: 'admin@company.com',
displayName: 'Super User',
adminLevel: 1
};
// ERROR: Argument of type 'AdminUser' is not assignable to parameter of type 'User'.
// Property 'timezone' is missing in type 'AdminUser' but required in type 'User'.
sendWelcomeEmail(admin);
Now your build is broken. You've created "parallel" types that are supposed to be related but aren't actually linked. You're now playing a game of "Whack-a-Mole," hunting down every single interface that you manually duplicated just to add one field.
Using Extends to Sync Requirements
The fix is to use the extends keyword. Instead of duplicating the properties, you tell TypeScript that AdminUser is a version of User with some extra bells and whistles. This creates a formal inheritance relationship.
interface User {
id: string;
email: string;
displayName: string;
timezone: string;
}
// AdminUser now automatically inherits everything from User
interface AdminUser extends User {
adminLevel: number;
}
const admin: AdminUser = {
id: 'a1',
email: 'admin@company.com',
displayName: 'Super User',
timezone: 'UTC', // TypeScript now forces us to include this
adminLevel: 1
};
sendWelcomeEmail(admin); // Works perfectly
By extending the interface, you've created a single source of truth. If you add a phoneNumber to User tomorrow, AdminUser will automatically require it too. I generally recommend this approach whenever you find yourself writing the same three or four properties in two different interfaces. It keeps your code DRY (Don't Repeat Yourself) and, more importantly, it makes your refactors significantly less painful.
One quick tip: you can actually extend multiple interfaces at once. If you have a Timestamped interface and a User interface, your AdminUser can extends User, Timestamped. It's a powerful way to compose complex objects from small, reusable pieces.
📋 Practical Task
Refactoring a Smart Home Device Hierarchy
You are building a system for a smart home hub. Currently, the code uses separate interfaces for different devices, leading to duplication and bugs. Your task is to refactor this using interface extension.
Current Broken State:
interface Device {
id: string;
manufacturer: string;
status: 'online' | 'offline';
}
interface Light {
id: string;
manufacturer: string;
status: 'online' | 'offline';
brightness: number;
}
interface Thermostat {
id: string;
manufacturer: string;
status: 'online' | 'offline';
temperature: number;
}
Your Requirements:
- Refactor
LightandThermostatto extend theDeviceinterface. - Add a new property
firmwareVersion: stringto theDeviceinterface. - Create a new interface called
SmartLightthat extendsLightand adds acolorTemp: numberproperty. - Create a constant
myLampof typeSmartLight. Ensure it contains all necessary properties fromDevice,Light, andSmartLight.
There are no comments for now.