Intersection Types in TypeScript

An intersection type combines multiple types into one. Learn the & syntax, how it differs from extends, and when to use intersection types over interface inheritance.

6 min read

An intersection type combines two or more types into a single type that has every property from all of them. You write it with the & operator: A & B means "a value that is both A and B at the same time." A variable of this type must satisfy every member of the intersection.

typescripttypescript
type Named = { name: string };
type Aged = { age: number };
 
type Person = Named & Aged;
 
const user: Person = {
  name: "Alice",
  age: 30
};

The Person type requires both name and age properties, since it combines the two smaller types into one. Leaving out a required field is rejected at compile time, before the object is ever used.

typescripttypescript
const invalid: Person = {
  name: "Bob"
};

TypeScript reports exactly which property from the intersection is missing, the same way it would for a single object type:

texttext
Type '{ name: string; }' is not assignable to type 'Person'.
  Property 'age' is missing in type '{ name: string; }'.

Union vs Intersection: A Quick Contrast

Union and intersection types solve opposite problems. Understanding the difference early prevents confusion.

ConceptSyntaxMeaningExample
UnionA | BValue is A or Bstring | number
IntersectionA & BValue has all properties of A and BNamed & Aged

With a union, you narrow to discover which specific type you have. With an intersection, you already know: the value must satisfy every type in the intersection.

Union vs intersection: which properties are accessible

A value of the union type only guarantees the properties that exist on both A and B, since it might be either one. A value of the intersection type guarantees every property from both, because it must be both at once.

Combining Object Types

The most common use of intersection types is mixing several small object shapes into one. Each piece describes a single concern, and the intersection assembles them into the full shape.

typescripttypescript
type HasId = { id: string };
type HasTimestamp = { createdAt: Date; updatedAt: Date };
type HasTitle = { title: string };
 
type Article = HasId & HasTimestamp & HasTitle;

A value typed as an Article must now satisfy all three smaller types at once, so every property from all three pieces becomes required on the same object:

typescripttypescript
const article: Article = {
  id: "art-001",
  title: "TypeScript Intersections",
  createdAt: new Date("2026-01-01"),
  updatedAt: new Date("2026-07-15")
};

This pattern keeps individual type definitions small and reusable, instead of writing one large interface that mixes unrelated concerns together.

Intersection with Primitives

Intersecting primitive types with other primitives usually resolves to never, because no value can be two different primitives at once.

typescripttypescript
type Impossible = string & number;
// Impossible is 'never' -- no value is both a string and a number
 
let x: Impossible;
// You can never assign a real value to x

Intersecting an object type with a primitive is different, because a primitive like a string already carries some built-in properties of its own.

typescripttypescript
type Weird = string & { length: number };

Since strings already have a length property of type number, the intersection is satisfied without conflict. This case is unusual in everyday code, but it shows how TypeScript checks each property individually instead of rejecting the whole combination.

Intersection vs Interface extends

Both the extends keyword and the ampersand can combine object types, and the result is often identical. There are still real differences in how TypeScript checks and reports each approach.

typescripttypescript
// With interface extends
interface Animal { name: string; }
interface Dog extends Animal { breed: string; }
 
// With intersection types
type AnimalType = { name: string; };
type DogType = AnimalType & { breed: string; };

Both resulting types require a name and a breed property, but they get there in different ways. The practical differences:

Featureinterface extendstype &
Declaration mergingInterfaces can mergeTypes cannot merge
Error messagesUses interface nameMay expand the type
Compiler performanceSlightly faster for large hierarchiesCan be slower with many intersections
Renaming primitivesCannot rename primitivesCan create aliases for any type

For extending a single base shape, the extends keyword is usually clearer to read. Reach for intersection types when you need to mix multiple independent types, or when you are already working with type aliases instead of interfaces.

Practical Pattern: Mixing Error Handling into Response Types

A common real-world pattern uses intersections to add the same error-handling fields to multiple response types, instead of repeating those fields on every interface.

typescripttypescript
interface ErrorHandling {
  success: boolean;
  error?: { message: string };
}
interface UserData {
  users: { id: string; name: string }[];
}
interface PostData {
  posts: { id: string; title: string }[];
}

Each interface above describes one concern on its own. Intersecting each data shape with the shared error-handling interface produces the two response types the application actually uses:

typescripttypescript
type UserResponse = UserData & ErrorHandling;
type PostResponse = PostData & ErrorHandling;

The error-handling type is written once and reused across every response type. If the error shape changes later, only one interface needs an update, and every response type that intersects with it picks up the change automatically:

typescripttypescript
function handleUserResponse(response: UserResponse) {
  if (!response.success) {
    console.error(response.error?.message);
    return;
  }
  console.log(`Loaded ${response.users.length} users`);
}

The success check narrows response before the users array is accessed, the same way narrowing works on any object type.

Intersection with Functions

Intersecting function types combines their call signatures into a single overloaded function. This is an advanced pattern, useful when one function needs to accept either a single value or a list of values.

typescripttypescript
type Logger = ((msg: string) => void) & ((msgs: string[]) => void);
 
const log: Logger = (input: string | string[]) => {
  if (Array.isArray(input)) {
    input.forEach(m => console.log(m));
  } else {
    console.log(input);
  }
};

Both call signatures must be satisfied by the same implementation, so the function body checks which shape it received before acting on it:

typescripttypescript
log("Hello");              // OK
log(["Hello", "World"]);   // OK

Common Intersection Mistakes

Intersecting types with conflicting properties. If two members define the same property name with incompatible types, the intersection resolves to never for that property.

typescripttypescript
type A = { value: string };
type B = { value: number };
type C = A & B;
 
// C['value'] is 'never' -- no value can be both string and number

Using intersections when a union is meant. Writing string and number together expecting "string or number" actually produces never, since no value can be both primitives at once. Use the pipe character for alternatives and the ampersand for combinations.

Forgetting that intersection adds all properties. A large intersection can produce a type with many required fields. Make sure the consumer of the type is actually expected to provide all of them.

Intersection with optional properties. When both types have the same optional property, the result is still optional. When one is required and the other is optional, the result is required.

typescripttypescript
type X = { name?: string };
type Y = { name: string };
type Z = X & Y;
 
// Z requires 'name' to be a string
const valid: Z = { name: "Alice" };

Intersection types work well alongside union types. Unions model alternatives; intersections model combinations. The discriminated union pattern takes this further by narrowing unions through a shared discriminant property.

Rune AI

Rune AI

Key Insights

  • An intersection type is written with & and combines all properties from every member type.
  • A value of type A & B must satisfy both A and B at the same time.
  • Intersection types are ideal for mixin patterns and composing reusable type pieces.
  • Conflicting property types in an intersection resolve to never.
  • Prefer extends for single inheritance; prefer & for merging independent type concerns.
RunePowered by Rune AI

Frequently Asked Questions

What is an intersection type in TypeScript?

An intersection type combines multiple types into a single type that has all the properties of every combined type. You write it with the & operator, like A & B. A value of this type must satisfy both A and B.

What is the difference between union and intersection types?

A union (A | B) means a value is either type A or type B. An intersection (A & B) means a value has all properties from both A and B. Unions are for alternatives; intersections are for combining.

Can intersection types conflict with each other?

Yes. If two intersected types have the same property with incompatible types, TypeScript resolves to never for that property. For primitives, string & number resolves to never since no value can be both.

Conclusion

Intersection types let you build a single type that combines the properties of several others. Use & to mix object shapes, mixins, and reusable type pieces. For extending a single interface, extends is usually clearer. For merging independent concerns like error handling and data shapes, intersection types are the right tool.