Type expression

Conditional types

  • Combining types
  • Tested with TypeScript 5.9

A conditional type, T extends string ? "text" : "other", checks whether T is assignable to string and becomes the first type if it is and the second if not. In JSDoc it goes in a @typedef with @template T, and it is written exactly as in TypeScript. TypeScript added conditional types in version 2.8.

Most code uses conditional types through the built-in utility types (Exclude, NonNullable, ReturnType, Awaited) rather than writing them. Reading one is still useful, because error messages and library types show them.

At a glance

Use JSDoc in a .js file TypeScript in a .ts file
A conditional type /** * @template T * @typedef {T extends string ? "text" : "other"} Kind */ type Kind<T> = T extends string ? "text" : "other";
With infer /** * @template T * @typedef {T extends Promise<infer U> ? U : T} Unwrapped */ type Unwrapped<T> = T extends Promise<infer U> ? U : T;

Examples

kind.js kind.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
/**
 * @template T
 * @typedef {T extends string ? "text" : T extends number ? "number" : "other"} Kind
 */

/** @type {Kind<string>} */
const a = "text";

/** @type {Kind<42>} */
const b = "number";

/**
 * @template T
 * @typedef {T extends Promise<infer U> ? U : T} Unwrapped
 */

/** @type {Unwrapped<Promise<string[]>>} */
const skus = ["TEE-01"];

export { a, b, skus };
type Kind<T> = T extends string ? "text" : T extends number ? "number" : "other";

const a: Kind<string> = "text";

const b: Kind<42> = "number";

type Unwrapped<T> = T extends Promise<infer U> ? U : T;

const skus: Unwrapped<Promise<string[]>> = ["TEE-01"];

export { a, b, skus };
A conditional type picks a type by testing another.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}
kind.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: kind.js(8,7): error TS2322: Type '"text"' is not assignable to type '"other"'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. kind.js(8,7): error TS2322: Type '"text"' is not assignable to type '"other"'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. kind.js(8,7): error TS2322: Type '"text"' is not assignable to type '"other"'. checked 21 October 2026
/**
 * @template T
 * @typedef {T extends string ? "text" : "other"} Kind
 */

/** @type {Kind<boolean>} */
const k = "text";error TS2322: Type '"text"' is not assignable to type '"other"'.

export { k };
The conditional type is checked like any other.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}
present.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: present.js(12,7): error TS2322: Type 'null' is not assignable to type 'string'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. present.js(12,7): error TS2322: Type 'null' is not assignable to type 'string'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. present.js(12,7): error TS2322: Type 'null' is not assignable to type 'string'. checked 21 October 2026
/**
 * @template T
 * @typedef {T extends null | undefined ? never : T} Present
 */

/** @typedef {Present<string | null | undefined>} Name */

/** @type {Name} */
const name = "Jane";
/** @type {Name} */
const missing = null;error TS2322: Type 'null' is not assignable to type 'string'.

export { name, missing };
Conditional types distribute over unions.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

infer

infer U inside the extends clause names a part of the type for use in the true branch: T extends Promise<infer U> ? U : T pulls the resolved type out of a promise. That is the idea behind Awaited and ReturnType.

Conditional types and unions

When the checked type is a bare type parameter and the argument is a union, the condition applies to each member separately and the results are joined. Present<string | null | undefined> in the third example becomes string, because the null and undefined members map to never. This is how NonNullable and Exclude work.

Gotchas

  • Wrap the type parameter in brackets, [T] extends [string], to test a union as a whole instead of member by member.
  • Deeply nested conditional types slow down checking and produce long messages; prefer a built-in utility type when one fits.

Questions

Can I write conditional types in JSDoc?

Yes. Put them in a @typedef with the @template parameters they use; the syntax inside the braces is TypeScript's.

Sources

Last reviewed 20 October 2026.