niryo
niryo

Reputation: 1575

Typescript: understanding union and Intersection types

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?

Upvotes: 31

Views: 21896

Answers (5)

Lesiak
Lesiak

Reputation: 26084

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
};

Upvotes: 22

Ahmad Akell
Ahmad Akell

Reputation: 59

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.

Upvotes: 5

Shura
Shura

Reputation: 667

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.

Upvotes: 7

latata
latata

Reputation: 1723

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',
};

Upvotes: 3

Roberto Zvjerković
Roberto Zvjerković

Reputation: 10157

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/

Upvotes: 3

Related Questions