Overview
In TypeScript, union and intersection types offer versatile ways to construct types by combining existing ones. This capability enhances code readability and maintainability by allowing developers to express complex type relationships and constraints explicitly. Utilizing these types effectively can lead to more robust and flexible codebases.
Key Concepts
- Union Types: Enable variables to store values of two or more different types.
- Intersection Types: Combine multiple types into one, incorporating the features of all constituent types.
- Type Guarding: Techniques for determining and working with specific types within unions or intersections.
Common Interview Questions
Basic Level
- What are union types, and how do they differ from intersection types in TypeScript?
- How can you use union types to enhance function parameter flexibility?
Intermediate Level
- Explain type guarding and its necessity when working with union types.
Advanced Level
- How can intersection types be leveraged to design a high-order function that enhances objects with multiple behaviors?
Detailed Answers
1. What are union types, and how do they differ from intersection types in TypeScript?
Answer: Union types in TypeScript allow a variable to be one of several types. This is denoted by the |
symbol between types. Intersection types, denoted by the &
symbol, combine multiple types into one, meaning the resulting type will have all properties from the constituent types.
Key Points:
- Union types are about "or" logic – a variable can be one type or another.
- Intersection types are about "and" logic – a type must satisfy all combined types.
- Both are powerful for modeling dynamic and complex type scenarios but apply in different contexts.
Example:
// Union type: A variable can either be a number or a string
let myVar: number | string;
// Intersection type: An object must be both TypeA and TypeB
type TypeA = { name: string };
type TypeB = { age: number };
type Person = TypeA & TypeB;
const person: Person = { name: "Alice", age: 30 }; // Must satisfy both TypeA and TypeB
2. How can you use union types to enhance function parameter flexibility?
Answer: Union types can make function parameters more flexible by allowing them to accept different types of arguments. This is especially useful in functions that need to handle variations in input data types while performing similar operations.
Key Points:
- Increases function versatility without overloading.
- Simplifies code by reducing the need for type checks inside the function.
- Enhances code readability by explicitly stating the accepted types.
Example:
// Function that accepts both numbers and strings as input
function logMessage(message: number | string) {
console.log(message);
}
logMessage(123); // Valid
logMessage("Hello, world!"); // Valid
3. Explain type guarding and its necessity when working with union types.
Answer: Type guarding is a technique used to narrow down the type of a variable within a conditional block. When dealing with union types, TypeScript needs a way to determine the specific type of a variable at runtime to access type-specific properties or methods. Type guards help in safely distinguishing between types within unions.
Key Points:
- Essential for accessing type-specific properties or methods.
- Prevents runtime errors by ensuring the correct type is used.
- Can be achieved using typeof
, instanceof
, or custom type guards.
Example:
function processInput(input: string | string[]) {
if (typeof input === "string") {
console.log(input.toUpperCase()); // Safe to use string methods
} else {
console.log(input.join(", ")); // Safe to use array methods
}
}
4. How can intersection types be leveraged to design a high-order function that enhances objects with multiple behaviors?
Answer: Intersection types can be used to design high-order functions that return objects blending multiple behaviors or properties. This pattern is useful for composing objects with distinct functionalities, creating a form of mixin.
Key Points:
- Facilitates the composition of objects with diverse capabilities.
- Enables the creation of flexible and reusable code components.
- Supports a clean and modular architecture by combining simpler types.
Example:
type Runnable = { run: () => void };
type Flyable = { fly: () => void };
function makeFlyingCar<T extends object>(obj: T): T & Runnable & Flyable {
return {
...obj,
run: () => console.log("Running"),
fly: () => console.log("Flying"),
};
}
const flyingCar = makeFlyingCar({ brand: "FutureTech" });
flyingCar.run(); // Outputs: Running
flyingCar.fly(); // Outputs: Flying
This guide outlines the foundational to advanced concepts of union and intersection types in TypeScript, providing practical examples for each level of understanding.