Type expression

Type inference for generics

  • Generics
  • Tested with TypeScript 5.9

In a .ts file you can write first<string>(rows). In a .js file you cannot: first<string>(rows) is read as two comparisons, first < string and > (rows), and tsc reports operator errors (TS2365 and TS2693), as the second example shows. Every type argument in JavaScript is inferred.

TypeScript infers from the arguments first, and from the type the result is assigned to when the arguments say nothing. When neither helps, a type parameter in a .js file becomes any, which turns checking off for that value.

At a glance

Use JSDoc in a .js file TypeScript in a .ts file
Set by the variable's type /** @type {{ items: number[] }} */ const counts = createStore(); const counts = createStore<number>();
Keep literal types /** @template const T */ <const T>

Examples

store.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: store.js(17,19): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. store.js(17,19): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. store.js(17,19): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. checked 21 October 2026
/**
 * @template T
 * @returns {{ items: T[] }}
 */
export function createStore() {
  return { items: [] };
}

// T has no inference source here, so in a .js file it becomes any: nothing is checked.
const loose = createStore();
loose.items.push(1, "two");

/** @type {{ items: number[] }} */
const counts = createStore();
counts.items.push(1);
counts.items.push("two");error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

export { loose, counts };
With nothing to infer from, a type parameter is any in a .js file.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}
list.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: list.js(13,13): error TS2365: Operator '>' cannot be applied to types 'boolean' and 'unknown[]'. list.js(13,19): error TS2693: 'string' only refers to a type, but is being used as a value here. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. list.js(13,13): error TS2365: Operator '>' cannot be applied to types 'boolean' and 'unknown[]'. list.js(13,19): error TS2693: 'string' only refers to a type, but is being used as a value here. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. list.js(13,13): error TS2365: Operator '>' cannot be applied to types 'boolean' and 'unknown[]'. list.js(13,19): error TS2693: 'string' only refers to a type, but is being used as a value here. checked 21 October 2026
/**
 * @template T
 * @param {T[]} items
 * @returns {T | undefined}
 */
export function first(items) {
  return items[0];
}

/** @type {unknown[]} */
const rows = ["TEE-01"];
const sku = first<string>(rows);error TS2365: Operator '>' cannot be applied to types 'boolean' and 'unknown[]'.error TS2693: 'string' only refers to a type, but is being used as a value here.

export { sku };
JavaScript has no syntax for explicit type arguments.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}
route.js route.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 const T
 * @param {T} routes
 * @returns {T}
 */
export function defineRoutes(routes) {
  return routes;
}

const routes = defineRoutes({ home: "/", cart: "/cart" });

/** @type {"/cart"} */
const cartPath = routes.cart;

export { cartPath };
export function defineRoutes<const T>(routes: T): T {
  return routes;
}

const routes = defineRoutes({ home: "/", cart: "/cart" });

const cartPath: "/cart" = routes.cart;

export { cartPath };
@template const keeps literal types.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

Steering inference without type arguments

Type the variable that receives the result: /** @type {{ items: number[] }} */ const counts = createStore(); makes T a number, as the first example shows, where the untyped loose gets any.

Or type an argument: a typed empty array, /** @type {string[]} */ ([]), passed to the function gives T its type. A cast on the call's result also works, but it only claims the type rather than checking it.

const type parameters

TypeScript 5.0 added const type parameters, which infer the narrowest literal types, as if the caller had written as const. In JSDoc the modifier goes before the name, @template const T, and with TypeScript 5.9 it works as in the third example: routes.cart is typed "/cart", not string.

Gotchas

  • The any fallback is specific to JavaScript files; in a .ts file the same call infers unknown.
  • A type parameter used only in the return type, as in createStore, can never be inferred from arguments. Consider an optional parameter that mentions it, or a default.

Questions

How do I pass a type argument to a generic function in JavaScript?

You cannot write one. Give the variable that receives the result a type with @type, type one of the arguments, or give the type parameter a default.

Sources

Last reviewed 20 October 2026.