Typescript: understanding union and Intersection types
Asked Answered
M

5

24

I am trying to get an intuition about Union and Intersection types in typescript, but I can't figure out this case: Playground Link

interface A {
    a: number;
}

interface B{
    b: boolean;
}



type UnionCombinedType = A | B;
type IntersectionType = A & B;

const obj: UnionCombinedType = {
    a: 6,
    b: true,
}

const obj2: IntersectionType = {
    a: 6,
    b: true,
}

Why am I allow to put both values in the intersection type? The intersection between the two interfaces is empty. If I read the & as AND then it's clear to me why it allows me to add both props, but then I should read the | keyword as OR and I would expect it to allow me to assign only a or b but not both.

Can someone give me some intuition about those types?

Mussorgsky answered 22/4, 2020 at 16:59 Comment(0)
N
17

Given the following:

interface A {
    a: number;
    c: number;
}

interface B{
    b: boolean;
    c: number;
}

Expression of Union type A | B is assignable to either A or B. It must have properties from A or B (or both)

const oA: A | B = {
    a: 6,
    c: 6
}

const oB: A | B = {
    b: true,
    c: 6
}

const oC: A | B = {
    a: 6,
    b: true,
    c: 6
}

But what operations does a type like A | B have? Only these that belong to both A and B

oA.c = 1; // valid

Intersection type A & B, if it is assignable to both A and B (and therefore must have properties of both A and B).

const obj: A & B = {
    a: 6,
    b: true,
    c: 1
}

Update

You ask "why does A & B in your example can take b prop? it's not assignable to type A"

This is clearly not true. Any type that has all properties of A can be assigned to A. Extra properties make no harm:

const aAndExtraProp = {
  a: 6,
  c: 1,
  d: 6
};

const ca0: A = aAndExtraProp;

You are probably confused by Excess Property Checks for object literals:

Object literals get special treatment and undergo excess property checking when assigning them to other variables, or passing them as arguments. If an object literal has any properties that the “target type” doesn’t have, you’ll get an error:

const ca1: A = {
  a: 6,
  c: 1,
  d: 6 //ERROR
};
Nullipore answered 22/4, 2020 at 17:9 Comment(6)
why does A & B in your example can take b prop? it's not assignable to type AMussorgsky
ok, it's all starting to be clearer to me. the only question left- so what is the difference between the two? it looks like in both cases we are speaking of props that belongs both to A and BMussorgsky
Think this way: variable of union type can accept object that is either A or B. If you use it you can access only common properties, or you must somehow differentiate between the two. In other languages this is typically done via runtime type check and a downcast. In typescript, discriminated union types are typically used. In contrast, variable of the intersection type accepts only objects that are both A and B (has properties of A and B as Typescript uses nominal typing). You can use any property from A and B.Nullipore
There should be c property in obj: A & B The error: Property 'c' is missing in type '{ a: number; b: true; }' but required in type 'A'.Fallingout
@Fallingout Indeed, good catch - I've corrected the codeNullipore
u have an error @Nullipore i.imgur.com/R0Sl17X.jpegSivas
B
7

Think of values you can assign to these types, not about intersecting and merging the types themselves.

type I = string & number;
type U = string | number;

const i: I = 5;

Fails, we want i to be both string and number at the same time somehow, but thats impossible (I is never here).

const u: U = 5;

u can be string OR number, so its ok

Going back to your example:

const obj2: IntersectionType = {
    a: 6,
    b: true,
}

Works fine because obj2 intersects IntersectionType (has both properties a and b). TypeScript loops over properties in obj2 and finds that all necessary properties of IntersectionType are present.

And

const obj: UnionCombinedType = {
    a: 6,
    b: true,
}

Typescript looks at obj... Does obj have properties required in A? Yes. Thats enough for TS.

If you add property c to both objects, TS will fail because it is unknown property for both types, but thats another story.

Brutal answered 25/6, 2022 at 14:26 Comment(1)
Thank you so much for this! I was struggling to understand this concept and your answer was the only one of dozens of fleshed out answers that finally led to my eureka moment. It was hard switching over from merging and intersecting the types to thinking about them in terms of assignable values, and your examples on intersection and union finally did it for me.Hardner
A
3

Think from the perspective of what a union type accepts. The set of things that can be assigned to a string | number field is the union of the set of all strings and the set of all numbers. The set of things that can be assigned to a string & number is nothing because there is no overlap in the set of all strings and the set of all numbers.

I think you're picturing it as a set operator on the fields rather than a set operator of the sets of what each type represents.

////////////////////////

This is mathematical terminology: a "type" is a set of all its possible values, e.g. boolean = {true, false};. Now union of types T and U is a set consisting of values in T or in U (i.e. T merged with U); similarly intersection of values in both T and U (i.e. common part of them).

A member is accessible iff it's on all possible values. For union, value of type T|U may be T or may be U. We don't know which so only common attributes are legal. As you can see, operation on type implies different but complementary operation on members. This is a consequence of de Morgan laws.

https://www.reddit.com/r/typescript/comments/9qduq3/why_is_type_intersection_called_like_that_its/

Anora answered 22/4, 2020 at 17:21 Comment(2)
why number & string is empty but A & B in my example isn't? I can see from your explanation why those definitions work well for primitive types, but it looks like it doesn't work like this for interfaces. maybe I am missing the difference between an interface and a type?Mussorgsky
Because there ARE objects that comform to both A and B interfaces. Objects like { a: 5, b: true, c: 'whatever' } comform to both interfaces, so the set of the intersection is not an empty set.Eads
L
3

Here are the examples that may you help understand how TS unions and intersections work:

interface A {
  a1: string,
  a2: string,
}

interface B {
  b1: string,
  b2: string;
}

type UnionAB = A | B;
type IntersectionAB = A & B;

const unionAB1: UnionAB = {
  a1: 'xxx',
  a2: 'xxx',
  b1: 'xxx',
  b2: 'xxx',
};

const unionAB2: UnionAB = {
  a1: 'xxx',
  a2: 'xxx',
};

const unionAB3: UnionAB = {
  b1: 'xxx',
  b2: 'xxx',
};

// Error
// Property 'a1' does not exist on type 'B'.
console.log(unionAB3.a1);

const unionAB4: UnionAB = {
  a1: 'xxx',
  a2: 'xxx',
  b2: 'xxx',
};

// Error
// Property 'b1' does not exist on type 'UnionAB'.
// Property 'b1' does not exist on type 'A'.
console.log(unionAB4.b1);


// Error
// Type '{ a1: string; b2: string; }' is not assignable to type 'UnionAB'.
// Property 'b1' is missing in type '{ a1: string; b2: string; }' but required in type 'B'.
const unionAB5: UnionAB = {
  a1: 'xxx',
  b2: 'xxx',
};

const intersectionAB1 : IntersectionAB = {
  a1: 'xxx',
  a2: 'xxx',
  b1: 'xxx',
  b2: 'xxx',
};

// Error
// Type '{ a1: string; a2: string; b1: string; }' is not assignable to type 'IntersectionAB'.
// Property 'b2' is missing in type '{ a1: string; a2: string; b1: string; }' but required in type 'B'.
const intersectionAB2 : IntersectionAB = {
  a1: 'xxx',
  a2: 'xxx',
  b1: 'xxx',
};

// Error
// Type '{ a2: string; b1: string; b2: string; }' is not assignable to type 'IntersectionAB'.
// Property 'a1' is missing in type '{ a2: string; b1: string; b2: string; }' but required in type 'A'.
const intersectionAB3 : IntersectionAB = {
  a2: 'xxx',
  b1: 'xxx',
  b2: 'xxx',
};
Lea answered 23/10, 2021 at 22:50 Comment(0)
A
3

Let's look at the meanings of the names of each of them, not the signs used.

That means don't look to the Union '|' as an exclusive or, and don't look to the Intersection '&' as a combination and.

Looking to the meaning of Union is a union of many things, for Intersection it's actually the intersection of two or more things.

Let's have an example:

interface Human{
 firstName: string;
 lastName: string;
 }

interface Programmer{
 programmingLanguage: string;
 salary: number;
}

//you can get values from both interfaces
const UnionType: Human|Programmer={firstName: "Will",lastName: "Smith",programmingLanguage:"Typescript"};


//you can't get any value from both interfaces because there is no intersection value between them, but you can take all the values, because in this case, the intersection value is all the values from both interfaces.
const IntersectionType: Human & Programmer={firstName: "Will",lastName: "Smith",programmingLanguage:"TypScript",salary:2300};

I highly recommend reading this article TypeScript and Set Theory, to get more understanding about the concept of Union & Intersection.

Abatement answered 26/2, 2023 at 7:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.