Intersection types (&)

Combine several object types into one value that must satisfy every member simultaneously.

Signature
type ColorfulCircle = Colorful & Circle;
function draw(shape: Colorful & Circle & { id: string }): void;
  • Added in TypeScript 1.6

Parameters

ParameterTypeDescription
$membersX & Y & ...Two or more constituent types separated by `&`. The result has every member of every constituent. Order does not affect the resulting type.
$conflicting propertyoptionalneverWhen two constituents declare the same property with incompatible types, the property's type becomes the intersection of those types, which for unrelated primitives is `never`.

Errors

tsc reports "Type 'X' is not assignable to type 'never'" when you try to supply a value for a property whose declared types conflicted, and "Property 'y' is missing in type" when an object literal satisfies only some of the constituents.

Changelog

  • PHP 3.9Intersections whose discriminant properties conflict now reduce to `never` for the whole type rather than surviving as an unsatisfiable object type, so some code that previously compiled is now correctly rejected.
  • PHP 4.2Improved handling of intersections involving `unknown` and template literal string types, which now reduce more aggressively.

An intersection type, written with &, describes a value that satisfies all of its constituents at once. Colorful & Circle has both color and radius, and a value assigned to it must supply both. It is the type-alias counterpart of interface B extends A, and the composition tool for mixins, decorated props and "this type plus a couple of extra fields".

Where it differs from `extends`

The difference is conflict handling, and it is the thing people get caught by. Two interface declarations that declare the same property with different types are a compile error at the declaration. An intersection is not: it intersects the property types too. { name: string } & { name: number } gives you name: never, a property no value can ever supply. The alias itself compiles happily; the error surfaces later, at every attempted assignment, often far from the cause.

When to reach for it

Reach for & when you are composing behaviour onto a type you do not own — a component's own props plus a library's, a domain object plus audit fields, a function type plus attached properties. Prefer interface extends when you own both sides, because it reports conflicts immediately instead of quietly producing never.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Add fields to a third-party props or options type without editing it.
  • Compose mixin capabilities onto a base object type.
  • Attach properties to a function type, such as a callable with a `displayName` or `cache`.
  • Model a record that carries both its domain fields and shared metadata such as `createdAt` and `id`.

Frequently asked questions

What is the difference between `A & B` and `interface C extends A, B`?

They produce the same shape when A and B are compatible, but they differ on conflicts. `extends` reports an error at the declaration if a property type clashes; the intersection quietly makes that property `never` and defers the error to every use. Prefer `extends` when you control both types.

Why is my intersected property typed `never`?

Because the constituents declared it with incompatible types and TypeScript intersected those types too. `string & number` has no inhabitants, so the property becomes `never`. Fix the conflicting declaration, or use `Omit` to drop the property from one side before intersecting.

Does `&` merge objects at runtime?

No. `&` is a type-level operator and disappears at compile time; it produces no code and copies nothing. You still have to build the combined value yourself, usually with object spread, and the type merely checks that the result has every required member.

More examples

Composing props without owning the base type

Prints "clicked" then "[primary] Save", then "clicked" and "[ghost dense] Cancel". `ButtonProps` requires every member of both constituents, so omitting `variant` or `label` fails to compile, while `dense` stays optional because optionality is a property of the member, not of the intersection.

Example · typescript
type BaseButtonProps = { label: string; onClick: () => void };
type Themed = { variant: "primary" | "ghost"; dense?: boolean };
type ButtonProps = BaseButtonProps & Themed;

function render({ label, variant, dense = false, onClick }: ButtonProps) {
  onClick();
  return `[${variant}${dense ? " dense" : ""}] ${label}`;
}

console.log(render({ label: "Save", variant: "primary", onClick: () => console.log("clicked") }));
console.log(render({ label: "Cancel", variant: "ghost", dense: true, onClick: () => console.log("clicked") }));

A callable value with attached properties

Prints "8 8 10" then "cache hits: 1 size: 2". Intersecting a function type with an object type produces something both callable and property-bearing. The `as Memoized` assertion is needed because the arrow function alone does not yet have `cache` or `hits`; the properties are attached on the following lines.

Example · typescript
type Fn = (n: number) => number;
type Memoized = Fn & { cache: Map<number, number>; hits: number };

const double = ((n: number) => {
  const hit = double.cache.get(n);
  if (hit !== undefined) { double.hits++; return hit; }
  const out = n * 2;
  double.cache.set(n, out);
  return out;
}) as Memoized;
double.cache = new Map();
double.hits = 0;

console.log(double(4), double(4), double(5));
console.log("cache hits:", double.hits, "size:", double.cache.size);

Discussion

  • Be the first to comment on this lesson.