Skip to Content
Course content

20: Extending Interfaces

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 Light and Thermostat to extend the Device interface.
  • Add a new property firmwareVersion: string to the Device interface.
  • Create a new interface called SmartLight that extends Light and adds a colorTemp: number property.
  • Create a constant myLamp of type SmartLight. Ensure it contains all necessary properties from Device, Light, and SmartLight.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.