- Abstract class
- Annotations
- Array
- Asserts
- Casting
- Class
- Conditional types
- Const
- Date object
- Decorators
- Default parameter
- Dictionary
- Enum
- Exclude type
- Extract type
- For loop
- forEach()
- Function
- Generic types
- Generics
- Index signature
- Infer
- Inheritance
- Interface
- Intersection types
- Keyof type operator
- Let
- Map type
- Mixin
- Module
- Namespace
- Never
- Object type
- Omit type
- Operator
- Optional parameter
- Partial type
- Pick type
- Promise
- Property
- Readonly type
- Record type
- Required type
- Satisfies operator
- Tuples
- Type alias
- Type assertion
- Type guard
- Type narrowing
- Typeof Type Operator
- Union
- Utility types
- Var
- Void
TYPESCRIPT
TypeScript Intersection Types: Syntax, Usage, and Examples
TypeScript intersection types allow developers to combine multiple types into one. They create a new type that merges the properties and methods of all included types. This powerful feature is part of TypeScript’s type system and offers greater control and precision in defining complex object structures.
Intersection types that TypeScript provides are especially useful when working with interfaces, object types, and functions that need to fulfill multiple roles. By understanding how and when to use them, you can build safer, more maintainable code.
What Are TypeScript Intersection Types?
Intersection types in TypeScript are created using the ampersand symbol (&
). When two or more types are combined using this operator, the result is a new type that includes all the members from each source type.
type A = { a: number };
type B = { b: string };
type AB = A & B; // AB has both 'a' and 'b' properties
The new type AB
must satisfy both A
and B
. This differs from union types (|
), which accept one type or another, not necessarily both.
Why Use Intersection Types in TypeScript?
Using TypeScript intersection types helps when you want to:
- Combine multiple types into a unified structure
- Extend functionality while preserving type safety
- Create complex objects with mixed capabilities
- Avoid duplication by composing types instead of redefining them
Intersection types enable better abstraction in codebases that grow over time. Instead of reinventing types for similar objects, you can merge existing ones.
Basic Syntax of Intersection Types
The syntax for creating an intersection type is straightforward:
type Combined = TypeA & TypeB;
Here’s a basic example:
type Person = { name: string };
type Contact = { email: string };
type ContactPerson = Person & Contact;
const user: ContactPerson = {
name: "Alex",
email: "alex@example.com"
};
In this example, the user
object must have both name
and email
properties. TypeScript enforces that the object meets all requirements from both Person
and Contact
.
Intersection Types vs Union Types
Understanding the difference between union and intersection is essential:
- Union types (
|
): Allow either one type or another. - Intersection types (
&
): Require all types to be present simultaneously.
type A = { id: number };
type B = { name: string };
type U = A | B; // Accepts either A or B
type I = A & B; // Must satisfy both A and B
Union types are useful for flexibility; intersection types offer more structure. For instance, if you want to allow multiple possible shapes for a function argument, use a union. If the object must meet multiple contracts at once, use an intersection.
Using Interfaces with Intersection Types
Intersection types work seamlessly with interfaces. They let you combine multiple interfaces into one.
interface Drawable {
draw(): void;
}
interface Clickable {
click(): void;
}
type UIElement = Drawable & Clickable;
const button: UIElement = {
draw: () => console.log("Drawing button"),
click: () => console.log("Clicking button")
};
This allows you to describe objects with multiple behaviors without creating deeply nested inheritance chains.
Function Parameters and Intersection Types
TypeScript intersection types are especially useful for function parameters when a function should accept objects that combine several characteristics.
interface Logger {
log(message: string): void;
}
interface Timestamped {
timestamp: Date;
}
function logEvent(event: Logger & Timestamped) {
console.log(`[${event.timestamp.toISOString()}]`);
event.log("Event occurred.");
}
The logEvent
function can only be called with an object that both logs messages and contains a timestamp.
Intersection Types with Classes
You can combine types from classes and interfaces using intersection types. This is particularly handy for extending behavior without altering class hierarchies.
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
}
interface Flyable {
fly(): void;
}
type FlyingAnimal = Animal & Flyable;
const bird: FlyingAnimal = {
name: "Eagle",
fly: () => console.log("Flying high")
};
Although the class Animal
is not extended directly, you can still merge its shape with other types dynamically.
Optional and Conflicting Properties
TypeScript allows merging optional properties in intersection types. However, if types contain conflicting property types, the result will not be assignable.
type A = { status: string };
type B = { status: number };
type C = A & B; // Error: 'string' is not assignable to 'number'
The conflict between status: string
and status: number
causes a type error because the compiler cannot resolve the contradiction.
When using TypeScript intersection type structures, be cautious of overlapping properties with different types.
Generic Intersection Types
Generics allow you to create reusable intersection types that work across different data shapes.
function merge<T, U>(a: T, b: U): T & U {
return { ...a, ...b };
}
const result = merge({ id: 1 }, { name: "Test" });
// result has both 'id' and 'name' properties
This generic merge
function returns a new object with the combined type of both input objects. TypeScript infers the intersection type automatically.
Intersection with Primitive Types
While intersection types work best with object types, they are technically allowed on any type. However, intersecting primitive types typically results in never
(an impossible type).
type A = string & number; // never
There’s no value that can be both a string and a number at the same time, so the result is unusable. Stick to objects, interfaces, and custom types for meaningful intersections.
Intersection Types in Real-World Use Cases
Component Composition in Front-End Frameworks
When using frameworks like React or Vue with TypeScript, components often need props from multiple sources. Intersection types help compose them clearly.
type OwnProps = { id: string };
type ReduxProps = { dispatch: Function };
type Props = OwnProps & ReduxProps;
You can then use Props
in your component without repeating the individual type definitions.
Combining API Response Shapes
Sometimes an API returns partial responses or combined data from multiple sources. You can describe these compound types using intersections.
type User = { id: number; username: string };
type Profile = { bio: string; avatar: string };
type FullUser = User & Profile;
This helps maintain clarity and type safety when consuming composite data structures.
When to Avoid Intersection Types
While powerful, TypeScript intersection types are not always the best solution. Avoid them in these scenarios:
- When the resulting type is too complex to understand or debug
- If properties from source types conflict
- When using primitives or unrelated shapes
It’s better to use composition, conditional types, or utility types if intersection logic gets too convoluted.
Related Utility Types
TypeScript also provides utility types that work well with intersections:
Partial<T>
— makes all properties optionalPick<T, K>
— selects a subset of propertiesOmit<T, K>
— removes specified keys
You can mix these with intersection types to build more expressive and flexible shapes.
type Config = { url: string; timeout: number };
type PartialConfig = Partial<Config> & { url: string };
Here, timeout
is optional, but url
remains required.
TypeScript intersection types offer a powerful way to compose multiple types into one. You can use them to enforce that an object or function satisfies all included types, giving your code better structure, safety, and reusability.
Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.