Typescript Union And Intersection Types Complete Guide

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

Understanding the Core Concepts of TypeScript Union and Intersection Types

TypeScript Union Types

Union types in TypeScript are utilized to create a type that can be one of many types combined using the pipe (|) symbol. This is particularly handy when a value might come in multiple forms and we want to enforce that a variable, argument or return type can be of more than one type.

Importance

Union types allow the code to be more expressive and error-resistant. They help catch errors at compile time and allow for handling multiple data types in a function or variable declaration without resorting to any or unknown.

Examples

let errorCode: number | string;
errorCode = 500;    // Valid number assignment
errorCode = "404"; // Valid string assignment

In this example, the errorCode variable can either be a number or a string. If you assign a different type of value, TypeScript will throw an error.

Another common use of Union Types is in functions where multiple parameter types are valid:

function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
    return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
    return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}

Here, the padLeft function allows the second parameter to be either a string or a number. It checks the type of padding to determine the appropriate processing.

TypeScript Intersection Types

Intersection types in TypeScript create a type by combining multiple types using the & symbol. Intersection types are often used to combine existing object types to create a new type with all the properties of the existing types. This technique is useful when two or more existing types should have a combination of features.

Importance

Intersection types are essential for creating more complex and sophisticated types. They enable building a composite type that combines the properties of multiple types into a single one, which is a powerful tool for type safety and ensuring that objects have all necessary attributes and functionalities.

Examples

interface Drivable {
  drive(): void;
}

interface Flyable {
  fly(): void;
}

type AirVehicle = Drivable & Flyable;

let vehicle: AirVehicle = {
  drive: () => console.log("Vroom vroom"),
  fly: () => console.log("Zoom zoom")
};

In the example above, AirVehicle is a type that combines the Drivable and Flyable types. So an object of type AirVehicle must implement both drive and fly methods.

Intersection types are also commonly used to extend the properties of objects:

type Person = {
  name: string;
  age: number;
};

type Employee = {
  employeeId: string;
  position: string;
};

type EmployeePerson = Person & Employee;

let employeePerson: EmployeePerson = {
  name: "John",
  age: 30,
  employeeId: "E123456",
  position: "Software Developer"
};

Here, EmployeePerson has properties from both Person and Employee making it a type that combines attributes from both.

Conclusion

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 Union and Intersection Types

Understanding TypeScript Union Types

A union type in TypeScript is a type that represents one of several possible data types. It allows you to declare a variable, parameter or return type that can be more than one type.

Syntax

type VarName = Type1 | Type2 | Type3 … | TypeN;

Step 1: Define Basic Union Types

Example: Define a variable that can be either string or number.

// Define a union type for a variable
let id: string | number = 101;

id = "abc"; // Valid
// id = true; // Invalid -> This will raise an error since boolean is not part of the union

Explanation:

  • The id variable is allowed to store either a string or a number.
  • Assigning a string or number value to id works.
  • Assigning a boolean or any other non-matching type raises a compile-time error.

Step 2: Using Union with Functions

Example: Create a function that accepts either a number or a string.

function printID(id: number | string) {
    console.log("Your ID is: " + id);
}

printID(101);   // Output: Your ID is: 101
printID("abc"); // Output: Your ID is: abc

Explanation:

  • The printID function parameter id can accept either a number or a string.
  • Inside the function, both types are treated as the intersection of properties that belong to both types (in this case, only methods/properties common to both number and string types).

Step 3: Narrowing in Union Types

To make better use of union types, it’s usually necessary to "narrow" them down from their combined set of types to a more specific one within our code. This can be done using typeof, instanceof, or custom type guards.

Example: Improve the printID function by narrowing the type based on typeof.

function printID(id: number | string) {
    if (typeof id === "string") {
        // Treat id as string if typeof id === "string"
        console.log("Your string ID is: " + id.toUpperCase());
    } else {
        // Treat id as number in all other cases
        console.log("Your numeric ID is: " + id.toFixed(2));
    }
}

printID(101);   // Output: Your numeric ID is: 101.00
printID("abc"); // Output: Your string ID is: ABC

Explanation:

  • The typeof operator is used to check the type of id.
  • Within the if block, TypeScript understands that id is a string and allows you to use string-specific methods like toUpperCase.
  • In the else block, TypeScript infers id must be a number, enabling the use of number-specific methods like toFixed.

Understanding TypeScript Intersection Types

An intersection type in TypeScript constructs a type by combining multiple types. It creates an object that has all of the features of the types from which it is intersected.

Syntax

type IntersectedType = Type1 & Type2 & Type3 … & TypeN;

Step 1: Creating Intersected Types

Example: Combine two interface types to form an intersection type.

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

interface Manager {
    managerID: number;
    managedEmployees: Array<Employee>;
}

// Intersection of Employee and Manager
type ManagerEmployee = Employee & Manager;

const managerEmp: ManagerEmployee = {
    employeeID: 1,
    department: "Technology",
    managerID: 2,
    managedEmployees: [
        { employeeID: 2, department: "Design" },
        { employeeID: 3, department: "Marketing" }
    ]
};

console.log(managerEmp);
// Output: { employeeID: 1, department: 'Technology', managerID: 2, managedEmployees: [ { employeeID: 2, department: 'Design' }, { employeeID: 3, department: 'Marketing' } ] }

Explanation:

  • The ManagerEmployee type is an intersection of Employee and Manager.
  • A managerEmp object must satisfy all property requirements from both interfaces (employeeID, department, managerID, and managedEmployees).

Step 2: Intersection with Multiple Types

Example: Combine multiple types to form an intersection.

type DetailedInfo = {
    age: number;
    address: string;
} & Employee & Manager;

const detailedPerson: DetailedInfo = {
    employeeID: 4,
    department: "HR",
    managerID: 5,
    managedEmployees: [],
    age: 30,
    address: "123 Elm St"
};

console.log(detailedPerson);
// Output: { employeeID: 4, department: 'HR', managerID: 5, managedEmployees: [], age: 30, address: '123 Elm St' }

Explanation:

  • The DetailedInfo type is an intersection of an anonymous type (age and address), Employee, and Manager.
  • The detailedPerson object must have all properties from each of these types.

Step 3: Combining Primitive Types

Although unions are more common with primitive types, intersections with basic types aren’t typically applicable, they can represent common functionality for objects. However, intersections can be used with utility types or in scenarios where a combination makes logical sense.

Example: Combine two primitive types (unconventional, but shown for completion).

// Unconventional - This doesn't make much sense but shows syntax
type Mixed = number & string;

// No valid assignment possible here as no value satisfies both conditions.
// const mixedVal: Mixed = 42; // Incorrect
// const mixedVal: Mixed = "fourtytwo"; // Incorrect

// Normally, intersections are used for objects or complex logic

Step 4: Using Intersections for Enhancing Functionality

Example: Enhance an existing type with additional properties.

interface BaseUser {
    userID: number;
    userName: string;
}

interface AuthenticatedUser extends BaseUser & { token: string } {}

const user: AuthenticatedUser = {
    userID: 6,
    userName: "johndoe",
    token: "abcd-efgh-ijkl-mnop"
}

console.log(user);
// Output: { userID: 6, userName: 'johndoe', token: 'abcd-efgh-ijkl-mnop' }

Explanation:

  • The AuthenticatedUser type extends BaseUser and includes an additional token property via an intersection.
  • The user object of type AuthenticatedUser includes all the required properties.

Complete Example with Code

Here is a comprehensive example showcasing both union and intersection types:

// Union Types
interface Dog {
    barks: boolean;
    numberOfLegs: number;
}

interface Cat {
    meows: boolean;
    numberOfLegs: number;
}

type Animal = Dog | Cat;

function isDog(animal: Animal): animal is Dog {
    return "barks" in animal;
}

// Intersection Types
interface Human {
    name: string;
    speaksInEnglish: boolean;
}

interface Engineer {
    skills: Array<string>;
    experienceYears?: number;
}

type EnglishEngineer = Human & Engineer;

const engineer1: EnglishEngineer = {
    name: "Jane Doe",
    speaksInEnglish: true,
    skills: ["TypeScript", "Data Structures"]
};

const engineer2: EnglishEngineer = {
    name: "John Smith",
    speaksInEnglish: false,
    skills: ["Machine Learning", "Deep Learning"],
    experienceYears: 5
};

console.log(engineer1); 
// Output: { name: 'Jane Doe', speaksInEnglish: true, skills: ['TypeScript', 'Data Structures'] }

console.log(engineer2);
// Output: { name: 'John Smith', speaksInEnglish: false, skills: ['Machine Learning', 'Deep Learning'], experienceYears: 5 }

// Function accepting union type
function animalDetails(animal: Animal) {
    console.log(`This animal has ${animal.numberOfLegs} legs`);
    if (isDog(animal)) {
        console.log(`It ${animal.barks ? "barks" : "does not bark"}`);
    } else {
        console.log(`It ${animal.meows ? "meows" : "does not meow"}`);
    }
}

const myDog: Dog = { barks: true, numberOfLegs: 4 };
const myCat: Cat = { meows: true, numberOfLegs: 4 };

animalDetails(myDog);  // Output: This animal has 4 legs \n It barks
animalDetails(myCat);  // Output: This animal has 4 legs \n It meows

Explanation:

  1. Union Types:

    • Defined Animal as a union of Dog and Cat. An Animal can be either a Dog or a Cat.
    • Created a type guard isDog to determine whether an Animal is a Dog, enabling narrowing.
  2. Intersection Types:

    • Defined EnglishEngineer as an intersection combining Human and Engineer. Represents someone who is both a human and an engineer.
    • Created examples of engineers who fulfill both the human and engineer criteria with or without optional properties.

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

1. What are Union Types in TypeScript?

Answer: Union types allow a variable, parameter, or function return type to have multiple possible types. They're useful when a value could be one of several types.

Example:

let value: string | number;
value = "Hello";  // Ok
value = 42;       // Ok

2. Could You Provide an Example of How to Use Union Types in Function Parameters?

Answer: Union types can be applied to function parameters to accept different types.

Example:

function printLength(val: string | any[]): number {
    return val.length;
}

console.log(printLength("Hello")); // Output: 5
console.log(printLength([1, 2, 3])); // Output: 3

3. How Do You Handle Cases Where You Need to Narrow Down the Type Within a Union?

Answer: In TypeScript, you can use type guards (like typeof, instanceof, or custom guards) to narrow down the type within a union.

Example:

function process(input: string | number): void {
    if (typeof input === 'string') {
        console.log(`String length: ${input.length}`);
    } else {
        console.log(`Number squared: ${input * input}`);
    }
}

4. What Are Intersection Types and How Do They Work?

Answer: Intersection types combine multiple types into one type, combining their properties. It’s used to create complex objects from two or more object types.

Example:

type User = {
    id: number;
    name: string;
};

type Permissions = {
    accessLevel: string;
};

type AdminUser = User & Permissions;

const admin: AdminUser = {
    id: 1,
    name: "Alice",
    accessLevel: "admin"
};

5. Can You Show Me an Example of Intersection Types Involving Interfaces?

Answer: Yes, you can intersect interfaces as well.

Example:

interface Car {
    seats: number;
    doors: number;
}

interface SportsCar {
    horsepower: number;
    speed: number;
}

type Supercar = Car & SportsCar;

const supercar: Supercar = {
    seats: 2,
    doors: 2,
    horsepower: 600,
    speed: 200
};

6. When Would You Use Union Types Instead of Intersection Types?

Answer: Use union types when a value can be one of several types, but not a combination of all types. Use intersection types when building complex structures which consist of properties from more than one type.

7. Is There Any Limitation with Using Union Types?

Answer: When using union types, TypeScript doesn't allow property access directly unless it's common across all types in the union. This requires type checking or assertion.

Example:

let val: string | number;
// val.toUpperCase();  // Error: Property 'toUpperCase' does not exist on type 'number'

if (typeof val === 'string') {
    console.log(val.toUpperCase());  // Now it’s safe!
}

8. Can You Explain Why Union Types Are Useful for Handling Different Event Data in Web Applications?

Answer: Absolutely. Union types help handle events where different event types might share common properties yet differ in other ways.

Example:

type KeyUpEvent = {
    type: "keyup";
    key: string;
};

type MouseDownEvent = {
    type: "mousedown";
    button: number;
};

function handleEvent(event: KeyUpEvent | MouseDownEvent) {
    if (event.type === "keyup") {
        console.log(`Key pressed: ${event.key}`);
    } else {
        console.log(`Button pressed: ${event.button}`);
    }
}

9. How Can I Use Literal Types Alongside Union Types for Better Code Clarity?

Answer: Literal types enhance union types by providing specific values rather than just types, adding clarity to what each union option represents.

Example:

type Direction = "north" | "south" | "east" | "west";

function move(direction: Direction): void {
    switch (direction) {
        case "north":
            console.log("Moving north...");
            break;
        case "south":
            console.log("Moving south...");
            break;
        case "east":
            console.log("Moving east...");
            break;
        case "west":
            console.log("Moving west...");
            break;
        default:
            console.error("Unknown direction!");
    }
}

10. Can You Illustrate a Real-world Scenario where Both Union and Intersection Types Are Used Together?

Answer: Imagine you have different types of employees (FullTime, PartTime), each with their own set of properties. Yet, every employee has some common properties like name and id. You can use union and intersection types together for this.

Example:

interface EmployeeBase {
    id: number;
    name: string;
}

interface FullTimeEmployee extends EmployeeBase {
    salary: number;
    benefits: string[];
}

interface PartTimeEmployee extends EmployeeBase {
    hourlyRate: number;
    hoursPerWeek: number;
}

type Employee = FullTimeEmployee | PartTimeEmployee;

const employee: Employee = {
    id: 1,
    name: "John Doe",
    salary: 50000,
    benefits: ["healthInsurance", "retirement"]
};

function describeEmployee(employee: Employee): void {
    console.log(`Employee ID: ${employee.id}, Name: ${employee.name}`);

    if ("salary" in employee) {
        console.log(`Full-time with Salary: $${employee.salary}`);
    } else {
        console.log(`Part-time with Hourly rate: $$${employee.hourlyRate}`);
    }
}

describeEmployee(employee);

In this scenario:

  • EmployeeBase provides common properties.
  • FullTimeEmployee and PartTimeEmployee extend EmployeeBase with unique properties.
  • Employee is a union of FullTimeEmployee or PartTimeEmployee.
  • The function describeEmployee() handles both cases using a type guard (in operator) to identify the correct employee type and access the appropriate properties.

You May Like This Related .NET Topic

Login to post a comment.