SyntaxStudy
Sign Up
TypeScript Interface Inheritance and Extension
TypeScript Beginner 1 min read

Interface Inheritance and Extension

Interfaces can extend one or more other interfaces using the extends keyword. The extending interface inherits all members of its parent interfaces and can add new ones. This creates an inheritance hierarchy for type contracts without any runtime overhead. Extending multiple interfaces at once is perfectly valid and is a common pattern for composing complex shapes from simpler ones. One of the unique features of TypeScript interfaces is declaration merging. If you declare an interface with the same name more than once in the same scope, the declarations are automatically merged into a single interface containing all members from every declaration. This is useful for augmenting third-party library types without modifying the library source. While interface extension is useful, it is important to distinguish it from type alias intersection. Interface extends enforces structural compatibility at the point of extension (the compiler checks for conflicts), whereas type intersections are computed lazily and may produce unexpected never types when properties conflict. For inheritance hierarchies, extends is generally the cleaner choice.
Example
// Base interface
interface Animal {
    name: string;
    age: number;
}

// Single inheritance
interface Pet extends Animal {
    owner: string;
}

// Multiple inheritance
interface ServiceAnimal extends Pet {
    certification: string;
    tasks: string[];
}

const guide: ServiceAnimal = {
    name: "Rex",
    age: 4,
    owner: "Maria",
    certification: "Guide-2023",
    tasks: ["navigation", "obstacle detection"],
};

// Declaration merging — augment an existing interface
interface Window {
    myAppVersion: string;
}
// Now Window has the extra property (useful for global augmentation)

// Extending to narrow an optional property to required
interface PartialUser {
    id: number;
    name?: string;
}

interface CompleteUser extends PartialUser {
    name: string; // narrows optional to required
}

const u: CompleteUser = { id: 1, name: "Bob" };

// Extending with generics
interface Repository<T> {
    findById(id: number): Promise<T | null>;
    save(entity: T): Promise<T>;
    delete(id: number): Promise<void>;
}

interface UserRepository extends Repository<CompleteUser> {
    findByEmail(email: string): Promise<CompleteUser | null>;
}