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

FeatureIntersection (&)Interface Extension (extends)
Works withType aliases, interfaces, primitivesInterfaces only
Conflict handlingResults in never on conflictError on incompatible property
Syntaxtype C = A & Binterface C extends A, B { }
Inline useYes — usable directly in parametersNo — 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

Leave a Comment