Direct answer: yes, an interface can extend another interface
In both TypeScript and Java, an interface extends one or more interfaces to form subinterfaces, inheriting method signatures without implementation. This mechanism enables declarative contracts, composable APIs, and safer refactoring. Below, we define key terms, contrast extends with implements, compare language behaviors, and outline limits and best practices to help you design stable, high-signal interfaces.
Core definitions and intent
An interface declares a named contract of methods, getters, and setters without implementation. Extending an interface means a subinterface inherits those signatures and may add new ones or refine types. Unlike classes, interfaces do not provide fields or method bodies (except in Java 8+ for default and static methods, and in TypeScript for optional method bodies via type-only constructs). The goal of extension is composition: building expressive, reusable type patterns while avoiding deep or fragile class hierarchies.
Extend versus implement: practical contrasts
Extends is used between interfaces; implements is used by classes (and enums) to fulfill contracts. A class can implement multiple interfaces but extends only one class, making extends on interfaces a form of horizontal composition. Interface extension does not create runtime inheritance; it is a compile-time type relationship. This minimizes runtime surprises and keeps extension strictly about type structure, which is valuable for scalable APIs and documentation.
TypeScript interface extension rules and examples
Syntax and capabilities
TypeScript uses extends with interfaces to merge declarations and support declaration merging. A subinterface inherits all members of the parent and can add new properties, methods, or index signatures, optionally widening or narrowing types for flexibility. TypeScript’s structural typing means compatibility depends on shape, not explicit inheritance, but extends provides clarity and self-documentation.
Concrete example
interface Animal {
name: string;
move(distance: number): void;
}
interface Mammal extends Animal {
warmBlooded: true;
giveBirth(): void;
}
const dog: Mammal = {
name: 'Rex',
move: (d) => console.log(`Moving ${d}m`),
warmBlooded: true,
giveBirth() { console.log('giving birth'); }
};
Multiple inheritance and mixins
TypeScript allows an interface to extend multiple interfaces, enabling mixin-like patterns. You can compose behaviors by stacking extends and then apply them to classes via implements. This is a compile-time mechanism; runtime behaviors must be provided by the implementing class or through helper functions that apply mixins.
Limitations and caveats
- Extends can only be used between interfaces, classes, or type aliases—not arbitrary types.
- Merging works primarily for plain object shapes; function or constructor signatures merge with care.
- TypeScript interfaces are erased at runtime, so extends does not affect JavaScript output directly.
- Excess property checks and readonly modifiers can affect assignment compatibility.
Java interface extension rules and examples
Single and multiple inheritance of type
Java interfaces support multiple inheritance: a subinterface can extend more than one parent. All methods from parent interfaces become part of the subinterface’s contract. Since Java 8, interfaces can contain default and static method implementations, but state remains limited to constants. Extending interfaces in Java is a way to compose types without the brittleness of deep class hierarchies.
Concrete example
interface Moveable {
void move(int distance);
}
interface Named {
String getName();
}
interface Animal extends Moveable, Named {
int LEGS = 4;
default String describe() {
return getName() + " has " + LEGS + " legs";
}
}
Restrictions and best practices
- An interface cannot extend a class; it can only extend other interfaces.
- Conflicting default methods from multiple parents must be resolved by the subinterface or implementing class.
- Fields in interfaces are implicitly public static final; avoid mutable state.
- Default methods are intended for safe evolution, not for rich behavior that belongs in classes.
When and how to use interface extension
Use extends when you want to create a specialized contract that is naturally a subset or refinement of a broader contract. Favor small, focused interfaces and prefer extension over large, monolithic definitions. In TypeScript, align extension with your type-checking strategy; remember structural compatibility often reduces the need for explicit extends. In Java, leverage multiple interface inheritance to compose APIs while avoiding stateful inheritance and conflicting defaults.
Common pitfalls and how to avoid them
- Over-nesting interfaces: deep chains make reading and maintenance harder.
- Assuming runtime behavior: extends is a compile-time construct; provide implementations in classes.
- Leaking implementation details through interfaces: keep them role-based and focused on capabilities.
- Ignoring method signature compatibility: incompatible overloads across parents can create ambiguous contracts.
Version-specific notes and compatibility
TypeScript’s behavior is consistent across recent versions, with declaration merging and structural typing stable since TypeScript 2.x. Java 8 introduced default and static methods to interfaces; Java 9 added private interface methods; Java 15 and later continue to evolve interface capabilities. Always check your compiler version when using default methods, const fields, or pattern matching features that may require newer language levels.