JavaScript and TypeScript course Β· Module 6: TypeScript Basics
Union and intersection types
In this lesson4
Welcome to the advanced TypeScript type laboratory in Jurassic Park! Dr. Henry Wu is especially excited today, because we will be discussing two powerful TypeScript mechanisms that can be compared to his most ambitious genetic engineering projects: union and intersection types.
Union Types - "either this or that"
A union type allows a variable to accept values of one of several different types. It's a bit like a hybrid enclosure in the park that can accommodate different species of dinosaurs, but only one kind at a time.
Basic syntax of union types
We create a union type using the | (pipe) operator:
1// A variable can be of type string OR number
2let identyfikatorDinosaura: string | number;
3
4identyfikatorDinosaura = "TREX-01"; // Ok, string is allowed
5identyfikatorDinosaura = 12345; // Ok, number is allowed
6// identyfikatorDinosaura = true; // Error! boolean is not part of the union string | numberPractical applications of union types
Union types are extremely useful in many practical scenarios in Jurassic Park:
1. Handling different identifier formats
1// Function that can accept different types of identifiers
2function findDinosaur(id: string | number): void {
3 // We must check the type before performing type-specific operations
4 if (typeof id === "string") {
5 console.log(`Looking for dinosaur with code: ${id.toUpperCase()}`);
6 } else {
7 console.log(`Looking for dinosaur with number: ${id.toFixed(0)}`);
8 }
9}
10
11findDinosaur("RAPTOR-BLUE"); // "Looking for dinosaur with code: RAPTOR-BLUE"
12findDinosaur(42); // "Looking for dinosaur with number: 42"2. Functions returning different types
1// Function that can return a dinosaur or an error message
2function pobierzDaneDinosaura(id: string):
3 { nazwa: string; species: string; wiek: number } | string {
4
5 // Simulating a database
6 const bazaDanych = new Map([
7 ["TRex1", { nazwa: "Rex", species: "Tyrannosaurus", wiek: 7 }],
8 ["Rap1", { nazwa: "Blue", species: "Velociraptor", wiek: 4 }]
9 ]);
10
11 const dinosaur = bazaDanych.get(id);
12
13 if (dinosaur) {
14 return dinosaur; // Returns dinosaur object
15 } else {
16 return `Dinosaur with ID not found: ${id}`; // Returns error message
17 }
18}
19
20const wynik = pobierzDaneDinosaura("TRex1");
21// We need to check the type of the returned value
22if (typeof wynik === "string") {
23 console.log(`Error: ${wynik}`);
24} else {
25 console.log(`Found: ${wynik.nazwa}, species: ${wynik.species}`);
26}3. Handling optional parameters
1// Function generating a report that can accept different filter parameters
2function generateDinosaurReport(
3 filtr?: { species?: string; minAge?: number; predators?: boolean }
4): string {
5 let raport = "Dinosaur report:\n";
6
7 if (filtr) {
8 raport += "Filters:";
9 if (filtr.species) raport += ` Species=${filtr.species}`;
10 if (filtr.minAge) raport += ` MinAge=${filtr.minAge}`;
11 if (filtr.predators !== undefined) raport += ` OnlyPredators=${filtr.predators}`;
12 } else {
13 raport += "No filters - showing all dinosaurs";
14 }
15
16 return raport;
17}
18
19console.log(generateDinosaurReport()); // Without filters
20console.log(generateDinosaurReport({ species: "Velociraptor" })); // With species filter
21console.log(generateDinosaurReport({ minAge: 5, predators: true })); // With multiple filtersDiscriminated Unions
Discriminated unions are an advanced technique of using union types with a "discriminating property" that allows TypeScript to recognize which type we're dealing with. It's like the dinosaur identification system in the park, which immediately tells whether we're dealing with a herbivore or a predator.
1// Defining interfaces with the discriminating property "typ"
2interface Herbivore {
3 typ: "herbivore";
4 dieta: string[];
5 poziomAgresji: number; // 1-10
6}
7
8interface Predator {
9 typ: "predator";
10 ofiary: string[];
11 biteForce: number; // in PSI
12}
13
14// Type that is a union of two interfaces
15type Dinosaur = Herbivore | Predator;
16
17// Function using the discriminated union
18function manageDinosaur(dino: Dinosaur): void {
19 // We check the discriminating property
20 switch (dino.typ) {
21 case "herbivore":
22 // TypeScript knows that dino is of type Herbivore here
23 console.log(`Prepare fodder: ${dino.dieta.join(", ")}`);
24 if (dino.poziomAgresji > 7) {
25 console.log("Warning: Herbivore with high aggression level!");
26 }
27 break;
28 case "predator":
29 // TypeScript knows that dino is of type Predator here
30 console.log(`Prepare meat for: ${dino.ofiary.join(", ")}`);
31 if (dino.biteForce > 10000) {
32 console.log("Warning: Very dangerous predator!");
33 }
34 break;
35 }
36}
37
38// Usage examples
39const triceratops: Herbivore = {
40 typ: "herbivore",
41 dieta: ["ferns", "cycads", "conifers"],
42 poziomAgresji: 6
43};
44
45const tRex: Predator = {
46 typ: "predator",
47 ofiary: ["triceratops", "edmontosaurus", "pachycephalosaurus"],
48 biteForce: 12800
49};
50
51manageDinosaur(triceratops);
52manageDinosaur(tRex);Intersection Types - "both this and that"
An intersection type allows combining multiple types into one, more complex type. It's like Dr. Wu's experiments with combining DNA from different dinosaurs to create entirely new species with the traits of all "ingredients".
Basic syntax of intersection types
We create an intersection type using the & operator:
1// Defining interfaces of basic traits
2interface BasicDinosaur {
3 id: number;
4 nazwa: string;
5 species: string;
6 wiek: number;
7}
8
9interface PhysicalTraits {
10 height: number; // in meters
11 length: number; // in meters
12 waga: number; // in kilograms
13}
14
15interface BehavioralTraits {
16 poziomAgresji: number; // 1-10
17 terytorialny: boolean;
18 social: boolean;
19}
20
21// Creating a type that is the intersection of three interfaces
22type FullDinosaurProfile = BasicDinosaur & PhysicalTraits & BehavioralTraits;
23
24// Now the object must implement all properties from all three interfaces
25const velociraptor: FullDinosaurProfile = {
26 // From BasicDinosaur
27 id: 101,
28 nazwa: "Blue",
29 species: "Velociraptor",
30 wiek: 4,
31
32 // From PhysicalTraits
33 height: 0.5,
34 length: 2,
35 waga: 15,
36
37 // From BehavioralTraits
38 poziomAgresji: 8,
39 terytorialny: true,
40 social: true
41};Practical applications of intersection types
Intersection types are especially useful when building complex systems in Jurassic Park:
1. Composing behaviors
1// Defining different abilities
2interface Swimming {
3 swimmingSpeed: number; // km/h
4 maxDepth: number; // meters
5 underwaterEndurance: number; // minutes
6}
7
8interface Flying {
9 wingspan: number; // meters
10 flightSpeed: number; // km/h
11 maxAltitude: number; // meters
12}
13
14// A dinosaur that can both swim and fly (like Pteranodon)
15type FlyingISwimmingDinosaur = BasicDinosaur & Flying & Swimming;
16
17const pteranodon: FlyingISwimmingDinosaur = {
18 id: 201,
19 name: "Pterri",
20 species: "Pteranodon",
21 age: 12,
22
23 // Flying abilities
24 wingspan: 8,
25 flightSpeed: 90,
26 maxAltitude: 500,
27
28 // Swimming abilities
29 swimmingSpeed: 20,
30 maxDepth: 10,
31 underwaterEndurance: 3
32};2. Extending existing types
1// Basic security interface for all enclosures
2interface BasicSecuritySystem {
3 doubleFence: boolean;
4 videoMonitoring: boolean;
5 motionSensors: boolean;
6}
7
8// Additional security measures for predators
9interface PredatorSecurityMeasures {
10 electricVoltage: number; // volts
11 wallThickness: number; // cm
12 securityDome: boolean;
13}
14
15// Full security system for predator enclosure
16type PredatorEnclosure = BasicSecuritySystem & PredatorSecurityMeasures;
17
18// Using intersection to add enclosure information
19type TRexEnclosure = PredatorEnclosure & {
20 area: number; // hectares
21 terrainLayout: string[];
22 realisticVegetation: boolean;
23};
24
25const rexEnclosure: TRexEnclosure = {
26 // BasicSecuritySystem
27 doubleFence: true,
28 videoMonitoring: true,
29 motionSensors: true,
30
31 // PredatorSecurityMeasures
32 electricVoltage: 10000,
33 wallThickness: 50,
34 securityDome: false,
35
36 // Specific to TRexEnclosure
37 area: 8,
38 terrainLayout: ["tall grass", "hills", "stream"],
39 realisticVegetation: true
40};3. Combining functionality in complex systems
1// Observation system interface
2interface ObservationSystem {
3 cameras: number;
4 resolution: string;
5 infraredTracking: boolean;
6
7 startRecording(): void;
8 stopRecording(): void;
9}
10
11// Alarm system interface
12interface AlarmSystem {
13 soundSignal: boolean;
14 lightSignal: boolean;
15 smsNotification: boolean;
16
17 triggerAlarm(level: number): void;
18 disableAlarm(): void;
19}
20
21// Complex security system combining both functionalities
22type ParkSecuritySystem = ObservationSystem & AlarmSystem;
23
24// Implementation of the security system
25class FullSecuritySystem implements ParkSecuritySystem {
26 // ObservationSystem
27 cameras: number = 24;
28 resolution: string = "4K";
29 infraredTracking: boolean = true;
30
31 startRecording(): void {
32 console.log("Starting recording from all cameras");
33 }
34
35 stopRecording(): void {
36 console.log("Stopping recording");
37 }
38
39 // AlarmSystem
40 soundSignal: boolean = true;
41 lightSignal: boolean = true;
42 smsNotification: boolean = true;
43
44 triggerAlarm(level: number): void {
45 console.log(`Activating level ${level} alarm!`);
46 if (this.soundSignal) console.log("Sirens activated");
47 if (this.lightSignal) console.log("Warning lights activated");
48 if (this.smsNotification) console.log("Sending SMS notifications to staff");
49 }
50
51 disableAlarm(): void {
52 console.log("Disabling all alarm systems");
53 }
54
55 // Additional methods specific to the full system
56 initiateSecurityProcedure(threatCode: string): void {
57 console.log(`Initiating security procedure: ${threatCode}`);
58 this.startRecording();
59 this.triggerAlarm(threatCode === "RED" ? 3 : 1);
60 }
61}
62
63// Using the system
64const securitySystem = new FullSecuritySystem();
65securitySystem.initiateSecurityProcedure("RED");Combining union and intersection - advanced cases
The true power of TypeScript is revealed when we combine union and intersection types into more complex structures - just as Dr. Wu combines DNA from different species to create entirely new dinosaurs.
1// Basic dinosaur types
2interface Dinosaur {
3 id: number;
4 name: string;
5 species: string;
6}
7
8interface LandDinosaur {
9 environment: "land";
10 runSpeed: number; // km/h
11}
12
13interface WaterDinosaur {
14 environment: "water";
15 swimmingSpeed: number; // km/h
16 maxDepth: number; // meters
17}
18
19interface AirDinosaur {
20 environment: "air";
21 flightSpeed: number; // km/h
22 maxAltitude: number; // meters
23}
24
25// Types for different abilities
26interface HuntingAbilities {
27 biteForce: number; // PSI
28 huntingStrategy: "ambush" | "chase" | "pack";
29}
30
31interface HerbivoreDefensiveAbilities {
32 armor: boolean;
33 horns: boolean;
34 spikes: boolean;
35 defensiveStrength: number; // 1-10
36}
37
38// Complex type using union and intersection
39type ParkDinosaur = Dinosaur &
40 (
41 (LandDinosaur & HuntingAbilities) |
42 (LandDinosaur & HerbivoreDefensiveAbilities) |
43 (WaterDinosaur & HuntingAbilities) |
44 (AirDinosaur & HuntingAbilities)
45 );
46
47// Examples of use
48const tyrannosaurus: ParkDinosaur = {
49 id: 1,
50 name: "Rex",
51 species: "Tyrannosaurus Rex",
52 environment: "land",
53 runSpeed: 30,
54 biteForce: 12800,
55 huntingStrategy: "chase"
56};
57
58const stegosaurus: ParkDinosaur = {
59 id: 2,
60 name: "Spike",
61 species: "Stegosaurus",
62 environment: "land",
63 runSpeed: 7,
64 armor: true,
65 horns: false,
66 spikes: true,
67 defensiveStrength: 8
68};
69
70// Function handling complex types
71function analyzeDinosaur(dino: ParkDinosaur): void {
72 console.log(`Analyzing: ${dino.name} (${dino.species})`);
73
74 // Check the environment
75 if (dino.environment === "land") {
76 console.log(`Running speed: ${dino.runSpeed} km/h`);
77
78 // Check whether it's a predator or herbivore
79 if ("biteForce" in dino) {
80 console.log(`Predator - bite force: ${dino.biteForce} PSI`);
81 console.log(`Hunting strategy: ${dino.huntingStrategy}`);
82 } else if ("defensiveStrength" in dino) {
83 console.log("Herbivore with defensive mechanisms:");
84 if (dino.armor) console.log("- Has armor");
85 if (dino.horns) console.log("- Has horns");
86 if (dino.spikes) console.log("- Has spikes");
87 console.log(`Defensive strength: ${dino.defensiveStrength}/10`);
88 }
89 } else if (dino.environment === "water") {
90 console.log(`Aquatic predator - swimming speed: ${dino.swimmingSpeed} km/h`);
91 console.log(`Maximum depth: ${dino.maxDepth} m`);
92 } else if (dino.environment === "air") {
93 console.log(`Aerial predator - flying speed: ${dino.flightSpeed} km/h`);
94 console.log(`Maximum flight altitude: ${dino.maxAltitude} m`);
95 }
96}
97
98analyzeDinosaur(tyrannosaurus);
99console.log("---");
100analyzeDinosaur(stegosaurus);Summary
Union and intersection types are like Dr. Wu's advanced genetic engineering techniques - they give us extraordinary power and flexibility in designing complex TypeScript systems.
| Type | Operator | Jurassic Park analogy | Use | |
|---|---|---|---|---|
| Union | ` | ` | Enclosure that can hold different dinosaurs (but only one kind at a time) | Variables that can have different types |
| Discriminated Union | ` | ` with discriminating property | Dinosaur identification system for different species | Safe handling of different type variants |
| Intersection | & | Combining DNA from different dinosaurs | Combining interfaces/types into more complex structures |
Dr. Wu summarizes today's lesson: "TypeScript, like our genetics laboratory, gives us enormous possibilities. Thanks to union and intersection types we can create systems precisely tailored to our needs, combining simple elements into complex structures. However remember - with great power comes great responsibility. Use this knowledge wisely to create a safe and reliable park."
Code for this lesson: index.ts
1// Union and intersection types in Jurassic Park
2console.log("Union and Intersection Types\n");
3
4// ===========================================
5// 1. UNION TYPES - type A or B
6// ===========================================
7console.log("=== UNION TYPES ===");
8
9// A simple union type
10type DinosaurDiet = "carnivore" | "herbivore" | "omnivore";
11
12let trexDiet: DinosaurDiet = "carnivore";
13let brachiosaurDiet: DinosaurDiet = "herbivore";
14
15console.log("T-Rex diet:", trexDiet);
16console.log("Brachiosaurus diet:", brachiosaurDiet);
17
18// Union with different types
19type ID = number | string;
20
21let paddockId: ID = "P-01";
22let dinosaurId: ID = 123;
23
24console.log("Paddock ID:", paddockId);
25console.log("Dinosaur ID:", dinosaurId);
26
27// Union with objects
28interface Carnivore {
29 type: "carnivore";
30 huntingStyle: string;
31 preySize: string;
32}
33
34interface Herbivore {
35 type: "herbivore";
36 plantPreference: string;
37 grazingHours: number;
38}
39
40type Dinosaur = Carnivore | Herbivore;
41
42const trex: Carnivore = {
43 type: "carnivore",
44 huntingStyle: "ambush predator",
45 preySize: "large"
46};
47
48const triceratops: Herbivore = {
49 type: "herbivore",
50 plantPreference: "ferns and cycads",
51 grazingHours: 16
52};
53
54// Type guards for union types
55function describeDiet(dino: Dinosaur): string {
56 if (dino.type === "carnivore") {
57 return `Carnivore: ${dino.huntingStyle}, preys on ${dino.preySize} animals`;
58 } else {
59 return `Herbivore: prefers ${dino.plantPreference}, grazes ${dino.grazingHours}h/day`;
60 }
61}
62
63console.log("\nT-Rex:", describeDiet(trex));
64console.log("Triceratops:", describeDiet(triceratops));
65
66// ===========================================
67// 2. INTERSECTION TYPES - type A and B
68// ===========================================
69console.log("\n=== INTERSECTION TYPES ===");
70
71// Combining types
72interface HasName {
73 name: string;
74}
75
76interface HasWeight {
77 weight: number;
78}
79
80interface HasDiet {
81 diet: DinosaurDiet;
82}
83
84// Intersection - must have all properties
85type CompleteDinosaur = HasName & HasWeight & HasDiet;
86
87const velociraptor: CompleteDinosaur = {
88 name: "Blue",
89 weight: 350,
90 diet: "carnivore"
91};
92
93console.log("Complete dinosaur:", velociraptor);
94
95// Extending interfaces through intersection
96interface BasicInfo {
97 id: number;
98 species: string;
99}
100
101interface LocationInfo {
102 paddockId: string;
103 zone: string;
104}
105
106interface HealthInfo {
107 isHealthy: boolean;
108 lastCheckup: Date;
109}
110
111type FullDinosaurInfo = BasicInfo & LocationInfo & HealthInfo;
112
113const rexy: FullDinosaurInfo = {
114 id: 1,
115 species: "Tyrannosaurus Rex",
116 paddockId: "P-01",
117 zone: "Carnivore Zone",
118 isHealthy: true,
119 lastCheckup: new Date()
120};
121
122console.log("\nFull info:", rexy);
123
124// ===========================================
125// 3. Combining Union and Intersection
126// ===========================================
127console.log("\n=== UNION + INTERSECTION ===");
128
129interface Swimmer {
130 swimSpeed: number;
131}
132
133interface Runner {
134 runSpeed: number;
135}
136
137interface Flyer {
138 flySpeed: number;
139}
140
141// Dinosaur can swim OR fly, BUT must be able to run
142type ActiveDinosaur = Runner & (Swimmer | Flyer);
143
144const mosasaurus: ActiveDinosaur = {
145 runSpeed: 5, // slow on land
146 swimSpeed: 50 // fast in the water
147};
148
149const pteranodon: ActiveDinosaur = {
150 runSpeed: 10,
151 flySpeed: 80
152};
153
154console.log("Mosasaurus:", mosasaurus);
155console.log("Pteranodon:", pteranodon);
156
157// ===========================================
158// 4. Practical uses
159// ===========================================
160console.log("\n=== PRACTICAL USES ===");
161
162// API Response type
163type SuccessResponse = {
164 status: "success";
165 data: any;
166};
167
168type ErrorResponse = {
169 status: "error";
170 message: string;
171 code: number;
172};
173
174type ApiResponse = SuccessResponse | ErrorResponse;
175
176function handleResponse(response: ApiResponse): void {
177 if (response.status === "success") {
178 console.log("Data received:", response.data);
179 } else {
180 console.log(`Error ${response.code}: ${response.message}`);
181 }
182}
183
184const success: ApiResponse = {
185 status: "success",
186 data: { dinosaurs: [trex, triceratops] }
187};
188
189const error: ApiResponse = {
190 status: "error",
191 message: "Paddock breach detected",
192 code: 503
193};
194
195handleResponse(success);
196handleResponse(error);
197
198// Event types
199interface BaseEvent {
200 timestamp: Date;
201 source: string;
202}
203
204interface FeedingEvent extends BaseEvent {
205 type: "feeding";
206 dinosaurId: number;
207 foodAmount: number;
208}
209
210interface EscapeEvent extends BaseEvent {
211 type: "escape";
212 dinosaurId: number;
213 paddockId: string;
214 severity: "low" | "high";
215}
216
217interface MaintenanceEvent extends BaseEvent {
218 type: "maintenance";
219 paddockId: string;
220 duration: number;
221}
222
223type ParkEvent = FeedingEvent | EscapeEvent | MaintenanceEvent;
224
225function logEvent(event: ParkEvent): void {
226 console.log(`[${event.timestamp.toISOString()}] ${event.source}`);
227
228 switch (event.type) {
229 case "feeding":
230 console.log(` Feeding dinosaur #${event.dinosaurId}: ${event.foodAmount}kg`);
231 break;
232 case "escape":
233 console.log(` ESCAPE from paddock ${event.paddockId}! Severity: ${event.severity}`);
234 break;
235 case "maintenance":
236 console.log(` Maintenance on paddock ${event.paddockId}: ${event.duration}h`);
237 break;
238 }
239}
240
241const feedingEvent: FeedingEvent = {
242 type: "feeding",
243 timestamp: new Date(),
244 source: "Automated System",
245 dinosaurId: 1,
246 foodAmount: 400
247};
248
249const escapeEvent: EscapeEvent = {
250 type: "escape",
251 timestamp: new Date(),
252 source: "Security Alert",
253 dinosaurId: 1,
254 paddockId: "P-01",
255 severity: "high"
256};
257
258console.log("\nEvent log:");
259logEvent(feedingEvent);
260logEvent(escapeEvent);
261
262// ===========================================
263// 5. Summary
264// ===========================================
265console.log("\n=== SUMMARY ===");
266console.log("Union Types (|) - value can be one of the types");
267console.log("Intersection Types (&) - value must satisfy all types");
268console.log("Type Guards - checking the type at runtime");
269console.log("Discriminated Unions - distinguishing by field");
270
271console.log("\nUnion and Intersection Types mastered!");Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What does the type string | number mean in TypeScript?
2. What does the type A & B (intersection type) do?
Hands-on tasks in the game
- Code editor
Create a union type DietType = 'herbivore' | 'carnivore' | 'omnivore' and an intersection type AquaticDino = Dinosaur & SwimmingAbility.
- Horizontal ordering
Arrange the elements of a type alias:
- Click in order
Arrange the elements of a union type definition:
- Vertical ordering
Arrange the steps of narrowing a union type in TypeScript: