Type expression

Intersection types

  • Combining types
  • Tested with TypeScript 5.9

An intersection {Product & Stored} describes a value that has everything Product has and everything Stored has. It is how JSDoc types are combined and extended: a @typedef cannot use extends, but @typedef {Base & { extra: string }} Extended does the same job.

A value of an intersection type must satisfy every part. An object literal missing the id that one part requires fails (TS2322, with the missing property named underneath), as the second example shows.

At a glance

Use JSDoc in a .js file TypeScript in a .ts file
Two types at once /** @typedef {Product & Stored} StoredProduct */ type StoredProduct = Product & Stored;
Adding a property inline /** @param {Product & { discount: number }} item */ (item: Product & { discount: number })

Examples

models.js models.ts TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
/** @typedef {{ sku: string, price: number }} Product */
/** @typedef {{ id: string, createdAt: Date }} Stored */
/** @typedef {Product & Stored} StoredProduct */

/** @type {StoredProduct} */
const tee = { id: "p_1", createdAt: new Date(), sku: "TEE-01", price: 19.5 };

/** @param {Product & { discount: number }} item */
export function salePrice(item) {
  return item.price * (1 - item.discount);
}

salePrice({ ...tee, discount: 0.2 });
export { tee };
type Product = { sku: string; price: number };
type Stored = { id: string; createdAt: Date };
type StoredProduct = Product & Stored;

const tee: StoredProduct = { id: "p_1", createdAt: new Date(), sku: "TEE-01", price: 19.5 };

export function salePrice(item: Product & { discount: number }) {
  return item.price * (1 - item.discount);
}

salePrice({ ...tee, discount: 0.2 });
export { tee };
Combining object types with &.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}
models.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: models.js(6,7): error TS2322: Type '{ sku: string; price: number; }' is not assignable to type 'Product & WithId'. Property 'id' is missing in type '{ sku: string; price: number; }' but required in type 'WithId'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. models.js(6,7): error TS2322: Type '{ sku: string; price: number; }' is not assignable to type 'Product & WithId'. Property 'id' is missing in type '{ sku: string; price: number; }' but required in type 'WithId'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. models.js(6,7): error TS2322: Type '{ sku: string; price: number; }' is not assignable to type 'Product & WithId'. Property 'id' is missing in type '{ sku: string; price: number; }' but required in type 'WithId'. checked 21 October 2026
/** @typedef {{ sku: string, price: number }} Product */
/** @typedef {{ id: string }} WithId */

/** @type {Product & WithId} */
const draft = { sku: "TEE-01", price: 19.5 };error TS2322: Type '{ sku: string; price: number; }' is not assignable to type 'Product & WithId'.
  Property 'id' is missing in type '{ sku: string; price: number; }' but required in type 'WithId'.

export { draft };
An intersection needs every part.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}
models.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: models.js(3,18): error TS2322: Type 'string' is not assignable to type 'never'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. models.js(3,18): error TS2322: Type 'string' is not assignable to type 'never'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. models.js(3,18): error TS2322: Type 'string' is not assignable to type 'never'. checked 21 October 2026
/** @type {{ id: string } & { id: number }} */
const record = { id: "p_1" };error TS2322: Type 'string' is not assignable to type 'never'.

export { record };
Conflicting property types intersect to never.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

Union or intersection?

A union A | B is one or the other: you may only use what they share until you narrow it. An intersection A & B is both at once: you may use everything from either. For object types the intersection has more properties and accepts fewer values.

Conflicting properties

When both parts declare the same property with incompatible types, the property's type is the intersection of the two, which may be never. { id: string } & { id: number } makes id impossible to set, and the error says a string is not assignable to never, as the third example shows.

Gotchas

  • Intersections of two different primitive types are never: string & number has no values.
  • Error messages show the expanded intersection, which can be long; a @typedef name keeps them readable.

Questions

How do I extend a @typedef in JSDoc?

Intersect it with the extra properties: /** @typedef {Product & { id: string }} StoredProduct */.

Sources

Last reviewed 20 October 2026.