- TypeScript Tutorial
- TypeScript Home
- TypeScript Introduction
- TypeScript Setup
- TypeScript First Program
- TypeScript vs JavaScript
- TypeScript Data Types
- TypeScript Type Inference
- TypeScript Type Annotations
- TypeScript Interfaces
- TypeScript Enums
- TypeScript Type Aliases
- TypeScript Type Assertions
- TypeScript Variables
- TypeScript Functions
- TypeScript Functions
- TypeScript Optional Parameters
- TypeScript Default Parameters
- TypeScript Rest Parameters
- TypeScript Arrow Functions
- Classes and Objects
- Introduction to Classes
- Properties and Methods
- Access Modifiers
- Static Members
- Inheritance
- Abstract Classes
- Interfaces vs Classes
- Advanced Types
- TypeScript Union Types
- TypeScript Intersection Types
- TypeScript Literal Types
- TypeScript Nullable Types
- TypeScript Type Guards
- TypeScript Discriminated Unions
- TypeScript Index Signatures
- TypeScript Generics
- Introduction to Generics
- TypeScript Generic Functions
- TypeScript Generic Classes
- TypeScript Generic Constraints
- TypeScript Modules
- Introduction to Modules
- TypeScript Import and Export
- TypeScript Default Exports
- TypeScript Namespace
- Decorators
- Introduction to Decorators
- TypeScript Class Decorators
- TypeScript Method Decorators
- TypeScript Property Decorators
- TypeScript Parameter Decorators
- Configuration
- TypeScript tsconfig.json File
- TypeScript Compiler Options
- TypeScript Strict Mode
- TypeScript Watch Mode
Intersection Types in TypeScript
In TypeScript, Intersection types allow you to merge multiple types into one. Think of it as a "Both/And" relationship. When you intersect two types, the resulting type will possess every single property and method from all the source types. This is a powerful feature for creating highly reusable and modular code without relying strictly on deep class inheritance hierarchies.
|) means "it could be A or B," while an Intersection (&) means "it must be A and B at the same time."
Key Concepts of Intersection Types
- Definition: Created using the
&(ampersand) operator, an intersection type combines the members of multiple types into a single unit. - Use Case: They are perfect for "Mixins" or when you need to combine existing data models, such as adding metadata to a database entity or merging API response parts.
- Structural Integrity: TypeScript's structural type system ensures that any object assigned to an intersection type satisfies all the combined contracts. If even one property is missing, the compiler will flag it.
Example Usage of Intersection Types
Example 1: Basic Intersection Type
Imagine you are building a system that tracks employees. Every employee is a person, but they also have professional attributes.
type Person = {
name: string;
age: number;
};
type Employee = {
role: string;
salary: number;
};
// Combining Person and Employee into one type
type EmployeeDetails = Person & Employee;
let employee: EmployeeDetails = {
name: "John",
age: 30,
role: "Senior Developer",
salary: 95000,
};
In this example, the EmployeeDetails type is the "sum" of Person and Employee. If you were to omit the salary property, TypeScript would throw an error because EmployeeDetails strictly requires everything from both definitions.
& acts like a logical "AND" in a way that restricts properties, but it actually accumulates them.
Example 2: Intersection with Multiple Types
You can intersect as many types as you need. This is common when building configuration objects or complex data structures.
type Address = {
street: string;
city: string;
};
type ContactInfo = {
email: string;
phone: string;
};
type SocialMedia = {
linkedIn?: string;
twitter?: string;
};
type FullContact = Address & ContactInfo & SocialMedia;
let contact: FullContact = {
street: "123 Tech Lane",
city: "San Francisco",
email: "[email protected]",
phone: "555-0199",
linkedIn: "linkedin.com/in/devprofile"
};
Here, FullContact aggregates three different type definitions. This modular approach allows you to reuse Address or ContactInfo in other parts of your application independently.
Example 3: Intersection with Interfaces
While type aliases are commonly used with intersections, you can also intersect interfaces. This is useful when you want to combine third-party library interfaces with your own local requirements.
interface Product {
id: number;
name: string;
}
interface Price {
price: number;
currency: string;
}
// Intersecting two interfaces to create a new type
type MarketableProduct = Product & Price;
let laptop: MarketableProduct = {
id: 101,
name: "MacBook Pro",
price: 2400,
currency: "USD"
};
id: string vs id: number), the intersection will result in never for that property, making the object impossible to instantiate.
Combining Classes with Intersection Types
Because TypeScript uses structural typing, an object can satisfy an intersection of classes even if it isn't an actual instance of those classes. It just needs to "look" like them by having the correct properties and methods.
class Car {
make: string = "";
model: string = "";
drive() {
console.log("The vehicle is moving.");
}
}
class Electric {
batteryLevel: number = 100;
charge() {
console.log("Charging battery...");
}
}
// This type requires everything from both Car and Electric
type ElectricCar = Car & Electric;
const myTesla: ElectricCar = {
make: "Tesla",
model: "Model 3",
batteryLevel: 85,
drive() { console.log("Silent driving..."); },
charge() { console.log("Plugged into Supercharger."); }
};
In this scenario, ElectricCar acts as a blueprint that enforces the presence of both "Car-like" and "Electric-like" behavior. This is often more flexible than using traditional class inheritance, as it avoids the "diamond problem" of multiple inheritance.
Using Intersection Types with Functions
Intersections can also be applied to function signatures. This is typically used to describe Function Overloading, where a single function might be able to handle multiple sets of parameters.
type Greet = (name: string) => string;
type Log = (message: string) => void;
// Combined, this function must be able to act as both (in specific contexts)
type LoggerGreeting = Greet & Log;
// Note: In practice, function intersections are mostly used for
// sophisticated library definitions and advanced type narrowing.
.d.ts).
Type Narrowing with Intersection Types
When you have an intersection type, you don't usually need to "narrow" the type to access properties because the object is guaranteed to have all of them. However, intersections are frequently used in Type Guards to safely merge properties during runtime checks.
type Admin = { role: string; permissions: string[] };
type User = { username: string; email: string };
type AdminUser = Admin & User;
function manageAccount(user: AdminUser) {
// We can safely access both Admin and User properties immediately
console.log(`Checking permissions for ${user.username}...`);
if (user.permissions.includes("admin_panel")) {
console.log(`Access granted to ${user.role} role.`);
}
}
const activeAdmin: AdminUser = {
username: "root_access",
email: "[email protected]",
role: "SuperAdmin",
permissions: ["read", "write", "admin_panel"],
};
manageAccount(activeAdmin);
By using AdminUser, you eliminate the need to constantly check if the role property exists on a standard User. The intersection makes the requirement explicit.
Summary
- The "And" Logic: Intersection types combine multiple definitions into one, requiring an object to satisfy all requirements simultaneously.
- Operator: Uses the
&symbol between types, interfaces, or classes. - Code Reuse: They facilitate a "composition-over-inheritance" design pattern, making your codebase more modular and easier to maintain.
- Strictness: TypeScript will prevent you from creating objects that are missing any piece of the combined intersection, ensuring high runtime reliability.