Typescript Intersection And Union Types Complete Guide

 Last Update:2025-06-22T00:00:00     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    9 mins read      Difficulty-Level: beginner

Understanding the Core Concepts of TypeScript Intersection and Union Types

TypeScript Intersection and Union Types

Introduction: TypeScript, a statically typed extension of JavaScript, introduces advanced type constructs that make it more powerful. Among these are Intersection Types and Union Types, which allow for flexible and accurate type definitions. Understanding and using these types correctly can significantly improve the readability and maintainability of your code.


1. Union Types

Definition: A Union Type allows us to define a type that could be one of several types. Essentially, it acts as an OR operator between types.

Syntax:

let variable: Type1 | Type2 | Type3;

Usage Example:

let studentId: number | string;

studentId = 12345;  // Valid
studentId = "S12345";  // Valid

Important Points:

  • When you declare a union type, TypeScript ensures that you only use the properties that are common across all possible types.
  • If your variable could be of multiple types, and you need to use a property/method available only in one of those types, you'll have to use a type guard to narrow down the type within a conditional block.

Type Guard Example:

function printLength(obj: string | number) {
    if (typeof obj === "string") {
        console.log(obj.length); // Type of obj is narrowed to string
    } else {
        console.log(obj.toString().length); // Type of obj is narrowed to number
    }
}

2. Intersection Types

Definition: An Intersection Type combines multiple types into one, creating a new type that has all the features of the combined types. It acts as an AND operator between types.

Syntax:

let variable: Type1 & Type2 & Type3;

Usage Example:

interface Bird {
    fly: () => void;
}

interface Fish {
    swim: () => void;
}

let pet: Bird & Fish;

pet.fly();  // Valid
pet.swim(); // Valid

Important Points:

  • Intersection types are useful when you need to create a single type that includes all the properties and methods of multiple types.
  • Intersection types are often used with generics to create highly flexible and reusable components.

Real-world Scenario: Imagine you are building a user management system where you want to represent an Admin that has both properties from User and Employee:

interface User {
    name: string;
    email: string;
}

interface Employee {
    employeeId: number;
    department: string;
}

type Admin = User & Employee;

const admin: Admin = {
    name: "John Doe",
    email: "john.doe@example.com",
    employeeId: 123,
    department: "IT"
};

3. Combined Examples

Intersection and Union Together: You can also mix intersection and union types to achieve greater type safety.

Usage Example:

interface Car {
    drive: () => void;
}

interface Boat {
    sail: () => void;
}

interface Airplane {
    fly: () => void;
}

type Vehicle = Car | Boat | Airplane;
let myVehicle: Vehicle & { wheels: number };

myVehicle = { 
    drive: () => console.log("Driving"),
    wheels: 4 
}  // Valid
myVehicle = { 
    sail: () => console.log("Sailing"),
    fly: () => console.log("Flying"),
    wheels: 0 
}  // Error: Type '({ sail: () => void; fly: () => void; wheels: number; } & Car & Boat & Airplane)' is not assignable to type 'Vehicle & { wheels: number; }'.

Explanation: In the above example, Vehicle can be either a Car, Boat, or Airplane. The myVehicle type is an intersection of Vehicle and an object that has a wheels property. Since Boat and Airplane do not have a drive method, the assignment would fail if only those types were used.


4. Conclusion

TypeScript Intersection and Union Types offer powerful ways to create more flexible and precise type definitions. They enable developers to build complex data models and functions that handle multiple types easily. By leveraging these type constructs, you can write more safe and maintainable code.

Always ensure you're using intersection and union types judiciously, and make use of type guards where necessary to prevent runtime errors. Mastery of these concepts will lead to better understanding and utilization of TypeScript's features, ultimately resulting in robust and scalable applications.


5. Key Takeaways

  • Union Types: Use | to create types that can be one of several types.
  • Intersection Types: Use & to create types that combine multiple types into one.
  • Type Guards: Narrow down types within conditional blocks to safely use type-specific properties/methods.
  • Flexibility: Mix intersection and union types to create highly flexible and precise type definitions.

Online Code run

🔔 Note: Select your programming language to check or run code at

💻 Run Code Compiler

Step-by-Step Guide: How to Implement TypeScript Intersection and Union Types

Intersection Types

Intersection types allow you to combine multiple types into one. The resulting type has all the features of the combined types.

Example 1: Combining Interfaces

// Define two interfaces
interface Employee {
    name: string;
    age: number;
}

interface Engineer {
    skills: string[];
    department: string;
}

// Create an intersection type that combines both Employee and Engineer interfaces
type EmployeeEngineer = Employee & Engineer;

// Create a function that prints details of an EmployeeEngineer
const printEmployeeEngineerDetails = (person: EmployeeEngineer) => {
    console.log(`Name: ${person.name}`);
    console.log(`Age: ${person.age}`);
    console.log(`Skills: ${person.skills.join(', ')}`);
    console.log(`Department: ${person.department}`);
};

// Create an object that satisfies the EmployeeEngineer type
const johnDoe: EmployeeEngineer = {
    name: "John Doe",
    age: 30,
    skills: ["Software Development", "Machine Learning"],
    department: "Engineering"
};

// Call the function with the object
printEmployeeEngineerDetails(johnDoe);

Explanation:

  • We define two interfaces Employee and Engineer.
  • The EmployeeEngineer type is an intersection of Employee and Engineer, meaning it must include properties from both.
  • In the johnDoe object, we need to provide all the required properties from both interfaces.
  • The function printEmployeeEngineerDetails accepts a parameter of type EmployeeEngineer and prints its details.

Example 2: Combining Classes

// Define two classes
class Animal {
    eat() {
        console.log("Eating...");
    }
}

class Mammal {
    breathe() {
        console.log("Breathing...");
    }
}

// Create an intersection type that combines both Animal and Mammal classes
type AnimalMammal = Animal & Mammal;

// Create a function that uses an instance of AnimalMammal
function performActions(animalMammal: AnimalMammal): void {
    animalMammal.eat();      // Methods from Animal class
    animalMammal.breathe();  // Methods from Mammal class
}

// Manually create an instance with combined behavior
let dogInstance: AnimalMammal = {
    eat: function() { 
        console.log("Dog eating..."); 
    },
    breathe: function() { 
        console.log("Dog breathing..."); 
    }
};

// Call the function with the combined instance
performActions(dogInstance);

Explanation:

  • We define two classes Animal and Mammal.
  • The AnimalMammal type combines the properties and methods of both Animal and Mammal.
  • We create an object dogInstance that includes all method implementations.
  • The performActions method can now use instances that implement both eat and breathe methods.

Union Types

Union types allow you to declare a variable that can hold any one of several types.

Example 1: Basic Union Type

// Define a union type that can be a number or a string
type NumberOrString = number | string;

// Create a function that accepts either a number or a string
function processInput(input: NumberOrString) {
    if (typeof input === 'number') {
        console.log(`Number processed: ${input}`);
    } else if (typeof input === 'string') {
        console.log(`String processed: ${input}`);
    }
}

// Use the function with different types
processInput(42);         // Pass a number
processInput("Hello!");   // Pass a string

Explanation:

  • The NumberOrString type is a union of number and string.
  • The processInput function checks the type of input before processing it accordingly.
  • This demonstrates how a function can handle different types within a single parameter.

Example 2: Union Type with Interfaces

// Define two interfaces
interface Bird {
    fly(): void;
}

interface Fish {
    swim(): void;
}

// Create a union type that can be either a Bird or a Fish
type BirdFish = Bird | Fish;

// Create functions that process each type separately
function makeBirdFly(bird: Bird) {
    bird.fly();
}

function makeFishSwim(fish: Fish) {
    fish.swim();
}

// Function to determine the type and call the appropriate method
function operateOnAnimal(animal: BirdFish): void {
    if ('fly' in animal) {
        makeBirdFly(animal);
    } else if ('swim' in animal) {
        makeFishSwim(animal);
    } else {
        throw new Error("Unknown animal type.");
    }
}

// Implement objects that satisfy either Bird or Fish
let birdInstance: Bird = {
    fly: function() { 
        console.log("Bird flying..."); 
    }
};

let fishInstance: Fish = {
    swim: function() { 
        console.log("Fish swimming..."); 
    }
};

// Call the function with different instances
operateOnAnimal(birdInstance);  // Output: "Bird flying..."
operateOnAnimal(fishInstance);  // Output: "Fish swimming..."

Explanation:

  • We define two interfaces Bird and Fish.
  • The BirdFish type declares a union between Bird and Fish.
  • We write separate functions to process the fly and swim actions.
  • The operateOnAnimal function uses the in keyword to check which method exists on the provided object and calls the appropriate function.
  • We implement two objects birdInstance and fishInstance satisfying their respective interfaces.

Example 3: Narrowing Down Union Types

// Define a union type that can hold different data types
type Data = string | number[] | boolean;

// Function to handle different cases of Data
function processData(data: Data): void {
    if (typeof data === 'string') {
        console.log(`Processing string: ${data}`);
    } else if (Array.isArray(data)) {
        console.log(`Processing array of ${data.map(item => item).join(', ')}`);
    } else if (typeof data === 'boolean') {
        console.log(`Processing boolean: ${data}`);
    }
}

// Test the processData function with various inputs
processData("Hello");
processData([1, 2, 3]);
processData(true);

Explanation:

  • The Data type is a union consisting of string, number[], and boolean.
  • The processData function identifies the exact type within the union using typeof and Array.isArray.
  • The console.log statements are conditional to the actual type provided during a function invocation, demonstrating proper type narrowing.

Summary:

  • Intersection Types: Combine multiple types into one, creating a type that includes all members of the combined types.
  • Union Types: Represent a value that could be one of several types.

Top 10 Interview Questions & Answers on TypeScript Intersection and Union Types

1. What are union types in TypeScript?

Answer: Union types in TypeScript allow a variable or parameter to be of one of multiple types. It is denoted using the pipe (|) symbol. For example, let value: string | number; means value can hold either a string or a number.

2. How do intersection types work in TypeScript?

Answer: Intersection types combine multiple types into one. They allow an object to have the properties and methods of all the types being intersected. Intersection types are created using the ampersand (&) symbol. For example, type Combined = TypeA & TypeB; means Combined will have all the properties from both TypeA and TypeB.

3. Can you provide an example of a union type in TypeScript?

Answer: Sure. Here’s an example:

function printId(id: number | string) {
    console.log(`Your ID is: ${id}`);
}
printId(101); // Output: Your ID is: 101
printId("202"); // Output: Your ID is: 202

In this example, the id parameter can accept both a number and a string.

4. Can you provide an example of an intersection type in TypeScript?

Answer: Definitely. Here’s an example:

interface Animal {
    name: string;
}

interface Dog {
    breed: string;
}

type DogAnimal = Animal & Dog;

const doggo: DogAnimal = {
    name: "Buddy",
    breed: "Golden Retriever"
};

In this example, DogAnimal type has both the name property from Animal and the breed property from Dog.

5. When would you use union and intersection types together?

Answer: Union and intersection types can be combined for complex type definitions. For instance, you might have a function that accepts an argument that can be multiple types and combines them:

interface Bird {
    fly(): void;
}

interface Fish {
    swim(): void;
}

type FlyingFish = Bird & Fish;

function move(animal: Bird | Fish | FlyingFish) {
    if ("swim" in animal) animal.swim();
    if ("fly" in animal) animal.fly();
}

const bird: Bird = { fly: () => console.log("Flying") };
const fish: Fish = { swim: () => console.log("Swimming") };
const flyingFish: FlyingFish = {
    fly: () => console.log("Flying Fish Flying"),
    swim: () => console.log("Flying Fish Swimming")
};

move(bird);        // Output: Flying
move(fish);        // Output: Swimming
move(flyingFish);  // Output: Flying Fish Flying
                    //         Flying Fish Swimming

6. What is the difference between a union and an intersection type?

Answer: The main difference lies in how they combine types. A union type (|) allows a value to be one of several types, whereas an intersection type (&) allows a type to be all of several types at once. Union types provide flexibility in the values that a variable can hold, while intersection types consolidate properties and methods from multiple types into a single type.

7. How do you handle optional properties in union types?

Answer: Optional properties in union types can be tricky because the TypeScript compiler does not know which type the variable holds at compile time. You often need to use type guards or the in operator to narrow the type. Here’s an example:

interface Circle {
    kind: "circle";
    radius: number;
}

interface Square {
    kind: "square";
    sideLength: number;
}

type Shape = Circle | Square;

function area(shape: Shape) {
    if (shape.kind === "circle") {
        return Math.PI * (shape.radius ** 2); // shape.radius is known at this point
    } else {
        return shape.sideLength ** 2; // shape.sideLength is known at this point
    }
}

In this example, the kind property acts as a discriminator to narrow the type.

8. Can you use union or intersection types in object literals?

Answer: Yes, both union and intersection types can be used in object literals. Here's an example:

type Person = {
    name: string;
};

type Employee = {
    employeeId: number;
};

type EmployeePerson = Person & Employee;

const employeePerson: EmployeePerson = {
    name: "John Doe",
    employeeId: 1001
};

type Contact = {
    email: string;
};

type PhoneContact = {
    phone: string;
};

type ContactMethod = Contact | PhoneContact;

const emailContact: ContactMethod = {
    email: "john.doe@example.com"
};

const phoneContact: ContactMethod = {
    phone: "1234567890"
};

9. Are there any common pitfalls when using union and intersection types?

Answer: Yes, some common pitfalls include:

  • Type Narrowing: Ensuring you correctly narrow types, especially with union types, to avoid runtime errors.
  • Property Conflicts: When using intersection types, make sure properties don't conflict with each other, as TypeScript will require both types to be compatible.
  • Complexity: Overusing union and intersection types can lead to complex and hard-to-read types. Keep your types as simple and readable as possible.

10. How do you effectively use union and intersection types for better type safety?

Answer: To improve type safety:

  • Use Discriminants: Add a common property (discriminant) to all the members of a union to help narrow types effectively.
  • Utilize Type Guards: Implement type guards to explicitly check which type your variable currently holds.
  • Simplify Intersections: Use interfaces and types strategically to compose complex types without unnecessary nesting or conflicts.

You May Like This Related .NET Topic

Login to post a comment.