Understanding TypeScript Intersection and Union Types
Intersection Types
Intersection types combine multiple types into a single type using the & operator, declared with type. For example:
interface Person {
name: string;
age: number;
}
interface Contact {
name: string;
phone: number;
}
type Combined = Person & Contact;
const data: Combined = {
name: 'John',
age: 30,
phone: 1234567890
};
Key Points
Can any types be merged with &?
- No, merging atomic types like
string & numberresults innever, as no value can satisfy both types simultaneously.
How are overlapping properties handled?
- If properties have the same name and type, they remain unchanged. If types differ, the merged property becomes
never.
interface First {
id: number;
label: string;
}
interface Second {
id: boolean;
extra: string;
}
type Merged = First & Second; // Property 'id' is of type 'never'
A more complex example:
interface ContainerA {
content: DataA;
}
interface ContainerB {
content: DataB;
}
interface ContainerC {
content: DataC;
}
interface DataA {
flag: boolean;
}
interface DataB {
text: string;
}
interface DataC {
count: number;
}
type AllContainers = ContainerA & ContainerB & ContainerC;
const instance: AllContainers = {
content: {
flag: true,
text: 'example',
count: 10
}
};
Union Types
Union types use the | operator to create a type that can be one of several types, representing the common subset. For instance, string | number allows values of either type.
Examples:
- Variable declaration:
let value: string | number | boolean;
value = 'text';
value = 42;
value = true;
- Merging interface types:
interface Alpha {
id: number;
desc: string;
code: string;
}
interface Beta {
id: number;
code: string;
}
type Gamma = Alpha | Beta;
const item: Gamma = {
id: 1,
code: 'ABC'
};
- Function interface union:
interface FuncX {
execute: () => string;
compute: () => number;
}
interface FuncY {
execute: () => string;
}
type FuncUnion = FuncX | FuncY;
function getFunc(): FuncUnion {
return {} as FuncUnion;
}
const funcInstance = getFunc();
funcInstance.execute();
// funcInstance.compute(); // Error: Property 'compute' does not exist on type 'FuncUnion'.
To differentiate between union members, use type guards like the in operator:
if ('compute' in funcInstance) funcInstance.compute();
Type Narrowing
- When literal types are combined with primitive types in a union, narrowing occurs:
type One = 'yes' | string; // Results in 'string'
type Two = true | boolean; // Results in 'boolean'
type Three = 5 | number; // Results in 'number'
- Enum members also exhibit narrowing:
enum Status {
Active,
Inactive
}
type State = Status.Active | Status; // Results in 'Status'
In unions, literal and enum member type are narrowed to their base types. For interfaces with overlapping properties of different types, consider this scenario:
interface TypeA {
identifier: string;
}
interface TypeB {
identifier: string | number;
[key: string]: any;
}
type CombinedAB = TypeA | TypeB;