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.
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.
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.
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:
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.
| Concept | Syntax | Meaning | Example |
|---|---|---|---|
| Union | A | B | Value is A or B | string | number |
| Intersection | A & B | Value has all properties of A and B | Named & 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.
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.
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:
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.
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 xIntersecting an object type with a primitive is different, because a primitive like a string already carries some built-in properties of its own.
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.
// 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:
| Feature | interface extends | type & |
|---|---|---|
| Declaration merging | Interfaces can merge | Types cannot merge |
| Error messages | Uses interface name | May expand the type |
| Compiler performance | Slightly faster for large hierarchies | Can be slower with many intersections |
| Renaming primitives | Cannot rename primitives | Can 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.
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:
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:
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.
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:
log("Hello"); // OK
log(["Hello", "World"]); // OKCommon 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.
type A = { value: string };
type B = { value: number };
type C = A & B;
// C['value'] is 'never' -- no value can be both string and numberUsing 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.
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
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.
Frequently Asked Questions
What is an intersection type in TypeScript?
What is the difference between union and intersection types?
Can intersection types conflict with each other?
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.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.