Comparison

JSDoc-typed JavaScript vs TypeScript files

The same checker reads both. What differs is syntax, the build step, and what each can express: a dated comparison with examples checked by tsc.

  • Examples checked with TypeScript 5.9

At a glance

Last verified 21 October 2026 with TypeScript 5.9.3; the TypeScript 7.0 notes come from one run of 7.0.2 on 20 October 2026.

Compared JavaScript with JSDocTypeScript (.ts)
Who checks it tsc, with checkJs or // @ts-checktsc
Where types are written In /** */ comments: @param, @type, @typedefIn the code: : string, interface, type
What runs The file you wrote, as isJavaScript produced from it by tsc, a bundler, or a runtime that strips types
Build step None to run the code; tsc only checksNeeded, unless the runtime strips types (Node.js 22.18 and later, Deno, Bun)
Errors reported The same codes from the same checkerThe same codes from the same checker
Syntax only one side has @enum and the older Closure forms, which TypeScript 7.0 dropsenum, namespace, abstract, interface, declare, parameter properties, type arguments in calls
Publishing types tsc writes .d.ts files from the JSDoctsc writes .d.ts files from the code
Looser by default Open object literals, optional untyped parameters, a value's name as a typeNone of these

One checker, two syntaxes

A .js file with JSDoc and a .ts file are read by the same checker, so the same mistake gets the same error. Switch tabs on the example to compare the two spellings of one module:

invoice.js invoice.ts Error confirmed · TypeScript 5.9.3 · checked 21 October 2026
/**
 * @typedef {object} Line
 * @property {string} sku
 * @property {number} quantity
 * @property {number} unitCents
 */

/**
 * @param {Line[]} lines
 * @returns {number}
 */
export function invoiceTotal(lines) {
  return lines.reduce((sum, line) => sum + line.quantity * line.unitCents, 0);
}

invoiceTotal([{ sku: "TEA-01", quantity: "2", unitCents: 450 }]);error TS2322: Type 'string' is not assignable to type 'number'.
interface Line {
  sku: string;
  quantity: number;
  unitCents: number;
}

export function invoiceTotal(lines: Line[]): number {
  return lines.reduce((sum, line) => sum + line.quantity * line.unitCents, 0);
}

invoiceTotal([{ sku: "TEA-01", quantity: "2", unitCents: 450 }]);error TS2322: Type 'string' is not assignable to type 'number'.
The same module in both spellings, and the same error.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["**/*.js"]
}

Everything that follows from the checker is shared: the error codes, strict and the other options, editor hovers and completions, and the .d.ts files tsc writes for a package. What differs is where the types are written and what runs.

What runs

The .js file runs exactly as written: Node.js, browsers, Deno and Bun all skip the comments. A .ts file has to become JavaScript first, either through a build (tsc, a bundler, a framework’s compiler) or through a runtime that strips the types as it loads the file, which Node.js does by default since 22.18 and 23.6 (JSDoc vs Node’s type stripping compares the two). Either way, the code you debug is the code you wrote only in the .js case, unless source maps bridge the gap.

What only TypeScript syntax can say

JSDoc covers types: every type expression TypeScript has works inside the braces, including generics, conditional and mapped types. What it cannot write is TypeScript syntax that does something at runtime or changes how the code is parsed: enum, namespace with code in it, parameter properties (constructor(private name: string)), abstract, and type arguments at a call site. In a .js file those are errors from the 8000 range, each pointing at the JSDoc to use instead:

cart.js Error confirmed · TypeScript 5.9.3 · checked 21 October 2026
export function cartTotal(prices: number[]): number {error TS8010: Type annotations can only be used in TypeScript files.error TS8010: Type annotations can only be used in TypeScript files.
  return prices.reduce((sum, price) => sum + price, 0);
}

export let itemCount: number = 0;error TS8010: Type annotations can only be used in TypeScript files.

export function first<T>(items: T[]): T | undefined {error TS8004: Type parameter declarations can only be used in TypeScript files.error TS8010: Type annotations can only be used in TypeScript files.error TS8010: Type annotations can only be used in TypeScript files.
  return items[0];
}
TypeScript annotations pasted into a .js file.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

The JSDoc replacements are ordinary JavaScript: a const object and a union @typedef instead of enum, a field and a constructor assignment instead of a parameter property, a typed argument instead of a type argument. TypeScript’s own erasableSyntaxOnly option (5.8) asks .ts files to give up the same runtime syntax, so code written either way is converging on the same subset.

Where JSDoc is looser

A .js file is read with a few allowances for existing JavaScript: an object literal can gain properties after it is created, an untyped parameter is optional, and a value’s name works as a type. How TypeScript reads a JavaScript file has the full list, with the .ts behavior beside each. TypeScript 7.0 removes several of them, so code written in the strict style checks the same in both.

Cost in practice

JSDoc is longer for the same types: a parameter type takes a tag line instead of a few characters after a colon, and a complex generic signature can take a paragraph. Teams that choose JSDoc usually accept that for the benefit of shipping the code they wrote; teams that choose TypeScript usually already have a build. The list of projects that check JavaScript with JSDoc records the reasons several large projects gave for their choice.

Which to choose

Choose JavaScript with JSDoc when…

  • The code must run as written: scripts, config files, browser code without a bundler, a library you want debuggable from node_modules.
  • You are adding types to an existing JavaScript codebase and want every step to ship without a new build.
  • The team reads and writes JavaScript, and types are documentation as much as checking.

Choose TypeScript files when…

  • You already have a build step (a bundler, a framework compiler) that handles .ts for free.
  • The code leans on types: many generics, overloads, conditional types, where JSDoc gets long.
  • You want enum, abstract classes, parameter properties or explicit type arguments in calls.

Questions

Is JSDoc checking as strict as TypeScript?

The checker is the same, and strict means the same thing in both. A .js file is looser in a few places by design (object literals that gain properties, optional untyped parameters), and TypeScript 7.0 removes several of those leniencies.

Can a project mix the two?

Yes. With allowJs, .ts and .js files import each other and share types; a .js file can @import an interface from a .ts file. Many projects keep application code in one and scripts or config files in the other.

Do JSDoc types slow anything down at runtime?

No. They are comments: the engine skips them. Like TypeScript's types, they exist only for the checker and the editor.

Sources

Last reviewed 21 October 2026.