Type intersections
&
in TypeScript is called the intersection operator.
Why tho? If you’re familiar with set intersections it might
seem like &
works exactly the wrong way around… like,
intersecting the sets {1, 2, 3}
and {2, 3, 4}
gives you
{2, 3}
— it’s the subset of elements that exist
in both sets.
But in Typescript, { foo: string } & { bar: number }
gives
{ foo: string, bar: number }
— we get all properties of either
type. Isn’t that more like a union?
Well
No.
There’s a very formal reason why the “type intersection” is called as such, and it has to do with the Liskov Substitution Principle.
Take a look at the following function definitions:
const foo = (x: { foo: string }) => x.foo;
const bar = (x: { bar: number }) => x.bar;
foo
takes Foo
s, and bar
takes Bar
s, but our intersection
type { foo: string } & { bar: number }
can be passed to either
function.
So if you imagine { foo: string }
and { bar: number }
as circles
on a Venn Diagram, then our intersection type is like the middle part.
Our foobar can go to the foo club and the bar club, so to speak, because
the foobar is a subtype of the foos and a subtype of the bars.
The Venn Diagram and it’s intersection in this case doesn’t show a subset relationship, but rather a subtype relationship!
It is kind of funny tho how the type with more properties will be a subtype of the type with fewer properties but that’s just what follows from the above.
And what follows from that is that the empty type {}
isn’t
actually a subtype of any other type, while the empty set {}
is a subset of every set. The “empty set” equivalent of types
would be the type that contains all properties, and where
each property can be anything.
Turns out typescript has not one but two built-in types for that:
unknown
, and any
.