Intersection Types
An intersection type combines several types into one that has all of their members, written with the ampersand (&) operator. Where a union (A | B) means “A or B,” an intersection (A & B) means “A and B simultaneously” — a value must satisfy every constituent type at once. Intersections are the primary tool for composing small, focused types into larger ones, and they pair naturally with mixins and capability-style modeling. This page covers how intersections behave, how they differ from interface extends, and the edge cases you should know about.
Combining object types
The most common use is merging object shapes. The resulting type requires every property from each member.
type HasId = { id: string };
type HasTimestamps = { createdAt: Date; updatedAt: Date };
type Entity = HasId & HasTimestamps;
const post: Entity = {
id: "p_1",
createdAt: new Date(),
updatedAt: new Date(),
};
// ❌ Error: missing 'updatedAt'
const bad: Entity = { id: "p_2", createdAt: new Date() };
Entity has id, createdAt, and updatedAt all at once. Composing reusable fragments like HasId and HasTimestamps keeps types DRY and lets you assemble exactly the shape each model needs.
Intersection vs union
It helps to think in terms of sets. A union is the broader set (a value from either type qualifies); an intersection is the narrower set (a value must belong to both). For object types this feels reversed from intuition: intersecting adds requirements, unioning relaxes them.
Union A | B | Intersection A & B | |
|---|---|---|
| Meaning | A or B | A and B |
| Object members | Only shared members accessible | All members from both required |
| Assignability | Accepts values of either type | Requires values matching both |
| Set analogy | Wider set | Narrower set |
| Typical use | Alternatives, optional values | Composing / mixing shapes |
This is why A & B on objects produces a more demanding type, while A | B produces a more permissive one.
Intersection vs interface extends
You can model “type A plus extra members” with either an intersection or an interface that extends. Both produce a combined shape, but they differ in conflict handling and merging.
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// Equivalent shape via intersection:
type DogAlias = Animal & { breed: string };
extends reports an error immediately if a derived member is incompatible with the base, whereas intersections silently combine. Prefer extends for clear hierarchical relationships and & when composing several independent fragments or mixing in computed/union types that interfaces cannot express.
Conflicting properties produce never
When you intersect object types whose properties have incompatible types, the conflicting property’s type collapses to never — a value satisfying both is impossible.
type A = { value: string };
type B = { value: number };
type AB = A & B; // { value: string & number } → { value: never }
// ❌ Error: Type 'string' is not assignable to type 'never'
const x: AB = { value: "hello" };
Because string & number has no possible value, the property becomes unusable. This usually signals a modeling mistake — if you meant “either a string or a number,” you want a union, not an intersection.
Intersecting primitives and functions
Intersecting unrelated primitives also yields never, since no value can be two primitives at once. Intersecting function types, by contrast, produces an overloaded callable.
type Impossible = string & number; // never
type Logger = (msg: string) => void;
type Timed = (ms: number) => void;
type Both = Logger & Timed; // callable as either overload
declare const fn: Both;
fn("hello"); // ok
fn(42); // ok
Reach for an intersection when you are adding capabilities to a type. If you find yourself intersecting types that share property names with different value types, you almost certainly want a union (or a discriminated union) instead.
Best Practices
- Use
&to compose small, single-purpose types (HasId,HasTimestamps) into richer ones. - Remember the set logic: union widens, intersection narrows — intersections add requirements.
- Prefer interface
extendsfor clear base/derived hierarchies; use&for ad-hoc composition. - Watch for properties collapsing to
never; it means you have incompatible members. - Don’t intersect when you mean “one or the other” — use a union instead.
- Keep composed fragments named and reusable so intersections stay self-documenting.
- Combine intersections with generics (
<T>() => T & HasId) to attach capabilities generically.