TypeScript Intersection Types
An intersection type combines multiple types into one. A value of an intersection type must satisfy all the combined types simultaneously. The ampersand symbol & creates an intersection. Where union types say "this OR that," intersection types say "this AND that."
Union vs Intersection
Union (A | B) — value must match A OR B:
+-----------+ +-----------+
| A | OR | B |
+-----------+ +-----------+
Value needs to satisfy only one
Intersection (A & B) — value must match A AND B:
+-----------+
| A |
| +-------+-------+
| | A & B | Value must have ALL
+---+ | | properties from both A and B
| B |
+-------+
Creating an Intersection Type
type HasName = {
name: string;
};
type HasAge = {
age: number;
};
// Intersection — must have both name AND age
type Person = HasName & HasAge;
let person: Person = {
name: "Seema",
age: 28
};
// Missing age causes an error
let invalid: Person = {
name: "Seema"
// Error: Property 'age' is missing in type '{ name: string; }' but required in type 'HasAge'
};
Combining Multiple Types
type Identifiable = {
id: number;
};
type Timestamped = {
createdAt: string;
updatedAt: string;
};
type Named = {
name: string;
description: string;
};
// Combine all three
type Product = Identifiable & Timestamped & Named;
let laptop: Product = {
id: 5001,
createdAt: "2025-01-10",
updatedAt: "2025-03-15",
name: "Gaming Laptop",
description: "High-performance laptop for gaming"
};
console.log(laptop.name + " | Created: " + laptop.createdAt);
// Output: Gaming Laptop | Created: 2025-01-10
Intersection with Interfaces
interface Flyable {
fly(): string;
altitude: number;
}
interface Swimmable {
swim(): string;
depth: number;
}
// FlyingFish must implement everything from both interfaces
type FlyingFish = Flyable & Swimmable;
let flyingFish: FlyingFish = {
altitude: 5,
depth: 10,
fly() { return "Flying at " + this.altitude + "m"; },
swim() { return "Swimming at " + this.depth + "m depth"; }
};
console.log(flyingFish.fly()); // Flying at 5m
console.log(flyingFish.swim()); // Swimming at 10m depth
Intersection in Function Parameters
type Serializable = {
serialize(): string;
};
type Loggable = {
log(): void;
};
function processData(data: Serializable & Loggable): void {
data.log();
let serialized = data.serialize();
console.log("Serialized: " + serialized);
}
let myData = {
value: 42,
serialize() { return JSON.stringify({ value: this.value }); },
log() { console.log("Processing value: " + this.value); }
};
processData(myData);
// Processing value: 42
// Serialized: {"value":42}
Adding Properties to an Existing Type
Intersection types provide a clean way to extend an existing type with additional properties without modifying the original.
type User = {
userId: number;
email: string;
};
// Extend User with admin-specific properties
type AdminUser = User & {
adminLevel: number;
canDeleteUsers: boolean;
};
let admin: AdminUser = {
userId: 1,
email: "admin@estudy247.com",
adminLevel: 5,
canDeleteUsers: true
};
console.log(admin.email); // admin@estudy247.com
console.log(admin.canDeleteUsers); // true
Conflict in Intersection Types
When two types in an intersection define the same property with different types, the result is an impossible type (never). TypeScript flags any attempt to assign a value to such a property.
type TypeA = {
value: string;
};
type TypeB = {
value: number; // Conflict: same property name, different type
};
type Conflict = TypeA & TypeB;
// "value" becomes "string & number" which equals "never"
// No value can be both a string and a number
let x: Conflict = {
value: "hello" // Error: string is not assignable to never
};
Avoid defining the same property with incompatible types in intersected types.
Intersection vs Extension Comparison
| Feature | Intersection (&) | Interface Extension (extends) |
|---|---|---|
| Works with | Type aliases, interfaces, primitives | Interfaces only |
| Conflict handling | Results in never on conflict | Error on incompatible property |
| Syntax | type C = A & B | interface C extends A, B { } |
| Inline use | Yes — usable directly in parameters | No — must be declared first |
Practical Example
// Role-based access in a learning platform
type BaseUser = {
userId: number;
name: string;
email: string;
joinedDate: string;
};
type StudentPermissions = {
canViewCourses: boolean;
canSubmitAssignments: boolean;
enrolledCourses: string[];
};
type InstructorPermissions = {
canCreateCourses: boolean;
canGradeStudents: boolean;
assignedCourses: string[];
};
type StudentUser = BaseUser & StudentPermissions;
type InstructorUser = BaseUser & InstructorPermissions;
let student: StudentUser = {
userId: 201,
name: "Pallavi Joshi",
email: "pallavi@mail.com",
joinedDate: "2025-01-15",
canViewCourses: true,
canSubmitAssignments: true,
enrolledCourses: ["TypeScript Basics", "React Essentials"]
};
let instructor: InstructorUser = {
userId: 101,
name: "Prof. Anand Rao",
email: "anand@estudy247.com",
joinedDate: "2024-06-01",
canCreateCourses: true,
canGradeStudents: true,
assignedCourses: ["TypeScript Basics"]
};
console.log(student.name + " enrolled in: " + student.enrolledCourses.join(", "));
// Pallavi Joshi enrolled in: TypeScript Basics, React Essentials
console.log(instructor.name + " teaches: " + instructor.assignedCourses.join(", "));
// Prof. Anand Rao teaches: TypeScript Basics
