11

In TypeScript, the following is called a union type:

number | string

and this is called an intersection type:

number & string

However, is the resulting type of the | not actually an intersection, and that of & a union?

From the docs:

For example, Person & Serializable & Loggable is a Person and Serializable and Loggable. That means an object of this type will have all members of all three types.

and

number | string | boolean is the type of a value that can be a number, a string, or a boolean.

If we have a value that has a union type, we can only access members that are common to all types in the union.


Q: Would it therefore not be more correct to switch their names?

2
  • 1
    This is an excellent question that comes up more than one might think. I had a fairly senior colleague with little experience in functional programming ask me this recently, and couldn't quite articulate the reasoning well.
    – Daniel
    Feb 23, 2020 at 22:08
  • 1
    "Union" can be read as "one or the other type", while "intersection" can be read as "one and the other type". By convention, "or" is called "union" and "and" is called "intersection". Feb 24, 2020 at 14:08

3 Answers 3

21

However, is the resulting type of the | not actually an intersection, and that of & a union?

No. These concepts come from functional programming and type theory, so to understand them, it helps if you think of types in a way that's more in line with that.

Informally, think of a type as a set of all the instances of that type. For example, the string type is conceptually a set of all concrete strings that you can construct, like "", "foo", "bar", "Hello, world!", etc. When you create an instance, it's like you are picking a single item from that set. If you come across some value, saying that it's of a certain type is like saying that it is contained in the set labeled by the type's name.

Now, when you define a union type number | string | boolean, you are practically doing a union operation on those three sets; you are saying that a variable of that type can be a number or a string or a bool - i.e., it can take some value from any of those three sets. In other words, you are pulling from a set which is a union of the three.

You can pick any one of these values

As for the intersection types, imagine, again, informally, placing things in sets based on what operations (and properties) they support. In JavaScript, if you call a method on an object, any object that has a method with a matching name and the appropriate semantics will work. TypeScript uses structural typing, meaning that it essentially follows the same principle (types are defined based on members), except that it adds support for compile-time checking. So, an object is considered to be of a certain type if it has the structure (methods and properties) required by that type.

This means that you can take a bunch of instances (objects), and group them into sets based on the operations they support. Some will belong to more than one set, like in the image below. When you write Person & Serializable & Loggable, you are restricting the set of possible values to only those that belong to all three sets simultaneously (and therefore support all these operations at the same time). I.e., you are allowing only those instances that are in their intersection.

You can only pick a value that's in the intersection

0
1

It would be a really bad idea to change the names of widely-used fundamental constructs. Intersection types have been studied since at least 1978, it would be extremely confusing after over 40 years to all of the sudden completely reverse their meaning, especially since their meaning is deeply rooted in type theory:

  • the set of values v that inhabit an intersection type S & T is exactly the intersection of the sets s and t that inhabit the types S and T: v ∈ S & T ⇔ v ∈ s ∩ t
  • the set of values v that inhabit a union type S | T is exactly the union of the sets s and t that inhabit the types S and T: v ∈ S | T ⇔ v ∈ s ∪ t
1

Values of type A & B has the union of the members of the types A and B.

That is, the values of type A & B are both of type A and of type B.

In other word, the set of possible values of type A & B is the intersection of the set possible values of type A and the set of possible values of type B. Why? Because for a value to be of type A & B it must be of type A and of type B, that is it must belong to both sets. That is an intersection.


The logic for A | B being a union is analogous to the one above. The values of type A | B are either of type A or of type B. In other words, the set of possible values of type A | B is the union of the set of possible values of type A and of type B... because for a value to be of type A | B it must be of type A or of type B, that is it must belong to either of the sets. That is a union.


I agree it is confusing. In particular if we are used to union types in other languages. Unions in Rust, for example. Where a value of the union type can be treated as either type.

You can pretend union types in those languages have all the members of both types. However, they share the same memory space. Thus, you cannot really get all possible combinations of values of the members.

What is important to notice is that a union types of A and B in those languages can represent any value that belongs to A and any value that belongs to B. That remains true in Typescript. A | B can be any value of type A or any value of type B.

On the other hand, an intersection type in Typescript can only represent values that are of both types at the same time.


You are doing operations with the sets of possible values, not the set of members.

4
  • "I agree it is confusing. In particular if we are used to union types in other languages. Unions in Rust, for example. Where a value of the union type can be treated as either type." – It's not really confusing if you realize that the thing that Rust calls a union has nothing to do with Union Types as asked in this question. The type of a Rust union is an Algebraic Sum Type, not a Union Type. Feb 29, 2020 at 11:08
  • @JörgWMittag XYZ is not confusing if you already know. Sure.
    – Theraot
    Feb 29, 2020 at 15:23
  • It seems pretty obvious even to me as a non-native speaker of English that the article you linked to uses the two words "union type" not as a single term (as is the case in this question) but rather to mean "the type of the Rust construct called 'union'", which is a different thing. Now, why the designers of Rust decided to call their sum types "union" and why the writers of the documentation chose to refer to the type of a union as "union type" instead of "sum type" or "type of a union", when there is already the established term "union type", that is a different question altogether. Feb 29, 2020 at 16:10
  • I don't think that's right. You can create sum types in Rust with an enum construct. union is a different beast. You'll rarely actually use unions in Rust, maybe some low-level stuff uses it, but I think it's mostly for interfacing with external libraries written in C (which also uses union for that construct).
    – Jasmijn
    Feb 7, 2021 at 12:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.