We use cookies to enhance your experience on the site
CodeWorlds

Union and intersection types

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 | number

Practical 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 filters

Discriminated 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."

Go to CodeWorlds