Skip to content
TypeScript ts aliases-unions 4 min read

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 | BIntersection A & B
MeaningA or BA and B
Object membersOnly shared members accessibleAll members from both required
AssignabilityAccepts values of either typeRequires values matching both
Set analogyWider setNarrower set
Typical useAlternatives, optional valuesComposing / 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 extends for 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.
Last updated June 29, 2026
Was this helpful?