While they may seem similar, there are distinct differences between them:
extends
is only applicable to types with statically known members:
type SimpleType = {x: string};
interface SimpleInterface extends SimpleType {} // valid
type ComplexType = {x: string} | {x: number};
// error: An interface can only extend an object type or intersection of object types with statically known members.
interface ComplexInterface extends ComplexType {}
Type intersection is a more general type operation that can be applied to any two types and provide a result; extends
is specifically for interfaces and interface-like types (as well as classes).
- When extending from a parent interface, creating fields with the same name but wider types is not allowed:
interface Parent {
x: string | number;
}
interface Child1 extends Parent {
x: string; // valid
}
interface Child2 extends Parent {
x: string | number | boolean; // error
}
However, type intersection does not have this restriction:
type IntersectedChild = Parent & {x: string | boolean}; // valid
// IntersectedChild will have property 'x' as an intersection
// of 'string | number' and 'string | boolean', resulting in a 'string':
type IntersectedChildX = IntersectedChild['x']; // string
- When using
extends
with classes, the child class inherits all implementations from its parent; whereas type intersection operates solely at the type level, so if you define classes A
and B
with type C = A & B
, C
remains a type rather than a class, requiring manual construction of an object that meets the constraints of C
(containing all members of A and B with matching visibility levels).
For further clarification, refer to this playground link with the examples showcased above.