DEV Community

Cover image for The meaning of union and intersection types
Milosz Piechocki
Milosz Piechocki

Posted on • Edited on • Originally published at codewithstyle.info

The meaning of union and intersection types

Union types are fairly popular in TypeScript. You might have already used them multiple times. Intersection types are slightly less common. They seem to cause a little bit more confusion.

Did you ever wonder where do those names come from? While you might have some intuition about what a union of two types is, the intersection is usually not understood well.

After reading this article, you will have a better understanding of those types which will make you more confident when using them in your codebases.

Simple union types

Union type is very often used with either null or undefined.

const sayHello = (name: string | undefined) => { /* ... */ };
Enter fullscreen mode Exit fullscreen mode

For example, the type of name here is string | undefined which means that either a string OR an undefined value can be passed to sayHello.

sayHello("milosz");
sayHello(undefined);
Enter fullscreen mode Exit fullscreen mode

Looking at the example, you can intuit that a union of types A and B is a type that accepts both A and B values.

Union and intersection of object types

This intuition also works for complex types.

interface Foo {
    foo: string;
    xyz: string;
}

interface Bar {
    bar: string;
    xyz: string;
}

const sayHello = (obj: Foo | Bar) => { /* ... */ };

sayHello({ foo: "foo", xyz: "xyz" });
sayHello({ bar: "bar", xyz: "xyz" });
Enter fullscreen mode Exit fullscreen mode

Foo | Bar is a type that has either all required properties of Foo OR all required properties of Bar. Inside sayHello it's only possible to access obj.xyz because it's the only property that is included in both types.

What about the intersection of Foo and Bar, though?

const sayHello = (obj: Foo & Bar) => { /* ... */ };

sayHello({ foo: "foo", bar: "bar", xyz: "xyz" });
Enter fullscreen mode Exit fullscreen mode

Now sayHello requires the argument to have both foo AND bar properties. Inside sayHello it's possible to access both obj.foo, obj.bar and obj.xyz.

Hmm, but what does it have to intersection? One could argue that since obj has properties of both Foo and Bar, it sounds more like a union of properties, not intersection. Similarly, a union of two object types gives you a type that only has the intersection of properties of constituent types.

It sounds confusing. I even stumbled upon a GitHub issue in TypeScript repository ranting about naming of these types. To understand the naming better we need to look at types from a different perspective.

Set theory

Do you remember a concept called sets from math classes? In mathematics, a set is a collection of objects (for example numbers). For example, {1, 2, 7} is a set. All positive numbers can also form a set (an infinite one).

Sets can be added together (a union). A union of {1, 2} and {4, 5} is {1, 2, 4, 5}.

Sets can also be intersected. Intersection of two sets is a set that only contains those numbers that are present in both sets. So, an intersection of {1, 2, 3} and {3, 4, 5} is {3}.

Let's imagine two sets: Squares and RedThings.

The union of Squares and RedThings is a set that contains both squares and red things.

However, the intersection of Squares and RedThings is a set that only contains red squares.

Intersection of sets

Relationship between types and sets

Computer science and mathematics overlap in many places. One of such places is type systems.

A type, when looked at from a mathematical perspective, is a set of all possible values of that type. For example the string type is a set of all possible strings: {'a', 'b', 'ab', ...}. Of course, it's an infinite set.

Similarly, number type is a set of all possible numbers: {1, 2, 3, 4, ...}.

Type undefined is a set that only contains a single value: { undefined }.

What about object types (such as interfaces)? Type Foo is a set of all object that contain foo and xyz properties.

Understanding union and intersection types

Armed with this knowledge, you're now ready to understand the meaning of union and intersection types.

Union type A | B represents a set that is a union of the set of values associated with type A and the set of values associated with type B.

Intersection type A & B represents a set that is an intersection of the set of values associated with type A and the set of values associated with type B.

Therefore, Foo | Bar represents a union of the set of objects having foo and xyz properties and the set of objects having bar and xyz. Objects belonging to such set all have xyz property. Some of them have foo property and the others have bar property.

Foo & Bar represents an intersection of the set of objects having foo and xyz properties and the set of objects having bar and xyz. In other words, the set contains objects that belong to sets represented by both Foo and Bar. Only objects that have all three properties (foo, bar and xyz) belong to the intersection.

Real-world example of intersection type

Union types are quite widespread so let's focus on an example of an intersection type.

In React, when you declare a class component, you can parameterise it with thy type of its properties:

class Counter extends Component<CounterProps> { /* ... */ }
Enter fullscreen mode Exit fullscreen mode

Inside the class, you can access the properies via this.props. However, the type of this.props is not simply CounterProps, but:

Readonly<CounterProps> & Readonly<{ children?: ReactNode; }>
Enter fullscreen mode Exit fullscreen mode

The reason for this is that React components can accept children elements:

<Counter><span>Hello</span></Counter>
Enter fullscreen mode Exit fullscreen mode

The children element tree is accessible to the component via children prop. The type of this.props reflects that. It's an intersection of (readonly) CounterProps and a (readonly) object type with an optional children property.

In terms of sets, it's an intersecion of the set of objects that have properties as defined in CounterProps and the set of objects that have optional children property. The result is a set of objects that have both all properties of CounterProps and the optional children property.

Summary

That's it! I hope this article helps you wrap your head around union and intersection types. As it's often the case in computer science, understanding the fundamentals makes you better at grasping programming concepts.

Want to learn more?

Did you like this TypeScript article? I bet you'll also like my course!

Want to learn more?

Did you like this TypeScript article? I bet you'll also like my book!

⭐️ Advanced TypeScript ⭐️

Top comments (4)

Collapse
 
anduser96 profile image
Andrei Gatej

This was a great article! Thank you.

I’m still not 100% sure I understood the union of 2 sets of objects.

So if you have Foo | Bar, wouldn’t it be similar to saying: just collect the objects from both sets?
Then, the intersection would result in { { xyz, foo }, { xyz, bar } }, right? Then, how can one infer that the actually resulted set would only be { xyz }? 🤔

Collapse
 
miloszpp profile image
Milosz Piechocki

Thanks!

Union would indeed contain all objects from both sets. However, the only thing we can know for sure about these objects is that they must contain the xyz property. Therefore, the type is { xyz: string }.

TypeScript has the concept of type assignability. An object type is assignable to { xyz: string } type even if it has some excess properties (there are exceptions, though - see here). So, all objects from both union members are assignable to { xyz: string } type.

Collapse
 
anduser96 profile image
Andrei Gatej

I think I finally understood it!

So it’s typescript’s magic that translates a union that resulted from a mathematical point of view into a single type that all the other types are assignable to.

So, without typescript, I’d get { { xyz, foo }, { xyz, bar } }, but after typescript weighs in, the resulted type will only be { xyz: string }.

I hope I got it right.

Thank you!

Thread Thread
 
miloszpp profile image
Milosz Piechocki

Yeah, I think you've got it right :)