Sjiep
Sjiep

Reputation: 758

Type safety difference between type and interface when extending

In listing the differences between type and interface within Typescript, it only mentions the different syntax.

Now up till now, I've been using type notation as my default, however I've had some situation where I want to define more specific types based on a "Base" type, which I'm not able to achieve using type, but apparently am able to achieve using interface.

Given the following code, would there be a way with using type to maintain type-safety as appears to happen with interface or is this an actual difference between type and interface that I can't find in the documentation?

type Vehicle = {
  wheelCount: number;
  size: 'small' | 'medium' | 'large'
}

type ExtendUsingType = Vehicle & {
  // This can be any value, doesn't even have to be of type string
  size: 'no-type-safety';
}

interface ExtendUsingInterface extends Vehicle {
  // it has to be either 'small', 'medium' or 'large'
  size: 'this-will-error'; 
}

Upvotes: 3

Views: 215

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250106

There is a different semantic meaning between intersection and extends.

An intersection means that for a value to be part of the intersection, it must satisfy both types (in terms of sets, the value must be in each of the sets defined by the two intersecting types). This might not always be possible. You could have types such as in your case that generate types that are uninhabitable by any value, basically resolving to never which is the empty set (and ExtendUsingType is resolved as never, although not all uninhabitable types are eagerly collapsed to never)

With extends you are saying that this new type, must be a subtype of the type you are extending. In terms of sets, the new interface must be a subset of the set defined by the base type. This means that you must satisfy this constraint of being a subset more rigorously, and will thus get more errors.

You should generally prefer extends to intersections. This both because you get more of a guarantee that the resulting type is indeed a subtype of the type you are extending, but also because you get better compiler performance (intersections have more complicated semantics and perform worse when type checked)

Intersections still have their uses, sometimes exactly because of the more relaxed checks (as mentioned in the docs). Branded types and defining a type with an index signature incompatible with some of the properties are both things that can only be done with intersections.

Upvotes: 3

Related Questions