Excellent follow-up ✅ — after unions, the natural next step is intersections.
Where unions mean “A or B”, intersections mean “A and B”.
They’re crucial for combining types, enforcing multiple constraints, and building reusable abstractions in TypeScript.
Here’s a deep, unique TypeScript article with practical examples.
TypeScript has two powerful operators for building complex types:
- Union (
|) → a value can be one of many types. - Intersection (
&) → a value must satisfy all given types simultaneously.
In this article, we’ll dive into intersections.
1. What is an Intersection Type?
An intersection combines multiple types into one.
The resulting type must have all the members of every type.
type A = { id: number };
type B = { name: string };
type AB = A & B;
const obj: AB = { id: 1, name: "Alice" }; // ✅ must have both
👉 You can think of intersections like set theory:
- Union → all elements from both sets.
- Intersection → only elements that belong to both sets at once.
2. Intersection with Objects
Intersections are commonly used to compose object shapes.
type Timestamps = { createdAt: Date; updatedAt: Date };
type User = { id: number; name: string };
type UserWithTimestamps = User & Timestamps;
const u: UserWithTimestamps = {
id: 1,
name: "Alice",
createdAt: new Date(),
updatedAt: new Date()
};
👉 This allows reuse of smaller types across models.
3. Intersection with Interfaces
Works the same with interface:
interface Logger { log: (msg: string) => void }
interface ErrorHandler { handleError: (err: Error) => void }
type Service = Logger & ErrorHandler;
const s: Service = {
log: (m) => console.log(m),
handleError: (e) => console.error(e.message)
};
4. Functions with Intersection Types
Intersections can describe functions with multiple capabilities:
type Fn1 = (x: number) => string;
type Fn2 = (y: string) => number;
type Combined = Fn1 & Fn2; // function must support BOTH
declare const f: Combined;
let a: string = f(42); // ✅ from Fn1
let b: number = f("text"); // ✅ from Fn2
👉 Rare but powerful for overloaded functions.
5. Intersection with Generics
Generics + intersections = reusable constraints.
function merge<T, U>(a: T, b: U): T & U {
return { ...a, ...b };
}
const user = { id: 1, name: "Alice" };
const timestamps = { createdAt: new Date() };
const result = merge(user, timestamps);
// result: { id: number; name: string; createdAt: Date }
6. Intersection with Utility Types
You can combine intersections with built-in utilities:
type User = { id: number; name: string };
type Admin = { role: "admin" };
type AdminUser = User & Admin;
Or with Partial and Readonly:
type ImmutableOptionalUser = Readonly<Partial<User>>;
7. Real-World Examples
(a) React Component Props
Sometimes you want to reuse base props and extend them.
type BaseProps = { disabled?: boolean };
type ButtonProps = BaseProps & { label: string; onClick: () => void };
const Button = ({ label, disabled, onClick }: ButtonProps) => (
<button disabled={disabled} onClick={onClick}>{label}</button>
);
(b) Angular Component Models
type Auditable = { createdAt: Date; updatedAt: Date };
type Product = { id: string; name: string };
type ProductModel = Product & Auditable;
@Component({...})
export class ProductCard {
@Input() product!: ProductModel;
}
(c) Vue with Composition API
type Size = { size: "small" | "large" };
type Color = { color: "red" | "blue" };
type ButtonProps = Size & Color;
export default defineComponent({
props: {
size: { type: String as PropType<"small" | "large">, required: true },
color: { type: String as PropType<"red" | "blue">, required: true }
}
});
(d) Node.js API Models
type DbEntity = { id: string };
type Timestamps = { createdAt: Date; updatedAt: Date };
type User = { name: string; email: string };
type UserEntity = DbEntity & Timestamps & User;
8. Intersection vs Union
| Feature | Union (|) | Intersection (&) |
|———|————-|———————|
| Meaning | Value can be A OR B | Value must be A AND B |
| Example | string | number → "hi" or 42 | {id} & {name} → must have both |
| Usage | Flexibility (alternative shapes) | Composition (combine features) |
9. Pitfalls of Intersections
- Conflicting properties
type A = { x: string }; type B = { x: number }; type C = A & B; // ❌ x cannot be string AND number→ Results in never forx. - Too many intersections = complexity
Overusing intersections can make code hard to maintain.
10. Best Practices
- ✅ Use intersections to compose models (
User & Timestamps). - ✅ Combine with generics for reusable patterns (
merge<T, U>). - ✅ Use in React/Angular/Vue props when extending base props.
- ❌ Avoid conflicting property names.
- ❌ Don’t intersect too many types — readability suffers.
11. Conclusion
- Intersection (
&) enforces all conditions at once. - Best for composition, code reuse, and strong modeling.
- Works with objects, functions, generics, and utility types.
- Use unions when you want choice, intersections when you want combination.
👉 Mastering both unions and intersections allows you to model real-world data precisely in TypeScript.