TypeScript 5.9 was announced on 1 August 2025, and it is the version this site checks every example with. For JavaScript projects it brought a much shorter tsc --init, the import defer syntax, a stable module: node20 setting and longer, more useful hovers in editors.
Its main breaking change is in the built-in types: ArrayBuffer is no longer a supertype of the typed arrays, so a Uint8Array or a Node.js Buffer passed where an ArrayBuffer is expected is now an error.
At a glance
| Release | TypeScript 5.9 |
|---|---|
| Released | 1 August 2025, the day of its announcement on the TypeScript blog |
| Source | TypeScript 5.9 release notes |
| Examples | Every example runs through TypeScript 5.9.3 when this site is built. |
| Previous release | TypeScript 5.8 |
| Next release | TypeScript 6.0 |
What changed for JavaScript
A shorter tsc --init
tsc --init writes a short tsconfig.json with current settings instead of a long commented list: module: nodenext, target: esnext, types: [], strict and several stricter checks. It does not set allowJs or checkJs; this site's quick start shows how to turn its output into a jsconfig.json.
import { readFile } from "node:fs/promises";
/**
* @param {string} path
* @returns {Promise<string[]>}
*/
export async function readLines(path) {
const text = await readFile(path, "utf8");
return text.split("\n");
}
const [first] = await readLines("notes.txt");
// With noUncheckedIndexedAccess, `first` is string | undefined.
console.log(first?.trim());jsconfig.json for this example
{
// Visit https://aka.ms/tsconfig to read more about this file
"compilerOptions": {
// File Layout
// "rootDir": "./src",
// "outDir": "./dist",
// Environment Settings
// See also https://aka.ms/tsconfig/module
"module": "nodenext",
"target": "es2024",
"types": ["node"],
// For nodejs:
// "lib": ["esnext"],
// "types": ["node"],
// and npm install -D @types/node
// Other Outputs
"sourceMap": true,
"declaration": true,
"declarationMap": true,
// Stricter Typechecking Options
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
// Style Options
// "noImplicitReturns": true,
// "noImplicitOverride": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
// "noFallthroughCasesInSwitch": true,
// "noPropertyAccessFromIndexSignature": true,
// Recommended Options
"strict": true,
"jsx": "react-jsx",
"verbatimModuleSyntax": true,
"isolatedModules": true,
"noUncheckedSideEffectImports": true,
"moduleDetection": "force",
"skipLibCheck": true,
"allowJs": true,
"checkJs": true,
"noEmit": true,
}
} import defer
import defer * as report from "./report.js" loads a module without running it; its code runs the first time one of its exports is read. Only namespace imports can be deferred, and TypeScript accepts the syntax only with module: preserve or esnext, because it does not rewrite it. Your runtime or bundler has to support it.
import defer * as report from "./report.js";
/** @param {number[]} values */
export function total(values) {
// report.js runs here, the first time sum is read
return report.sum(values);
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "esnext",
"types": [],
"moduleResolution": "bundler"
},
"include": [
"*.js"
]
} module node20
"module": "node20" models Node.js 20 and, unlike nodenext, will not change in later releases. It implies target: es2023, where nodenext implies esnext.
Longer hovers and DOM summaries
Hovers on DOM APIs now include a short summary from MDN, hovers can be expanded to show the types inside a type (a preview in VS Code), and the maximum hover length is larger and configurable (js/ts.hover.maximumLength in VS Code). All of it works in .js files, where hovers are how you read the types JSDoc gives your code.
A Uint8Array is not an ArrayBuffer
The built-in types changed so that ArrayBuffer is no longer a supertype of Uint8Array and the other typed arrays, nor of Node.js Buffer. Code that passes a typed array where an ArrayBuffer is declared now fails (TS2345). Pass bytes.buffer, or declare the parameter as Uint8Array. Errors that mention Buffer usually mean @types/node needs updating.
/** @param {ArrayBuffer} buffer */
function byteCount(buffer) {
return buffer.byteLength;
}
const bytes = new Uint8Array([1, 2, 3]);
export const loose = byteCount(bytes);error TS2345: Argument of type 'Uint8Array<ArrayBuffer>' is not assignable to parameter of type 'ArrayBuffer'.
Types of property '[Symbol.toStringTag]' are incompatible.
Type '"Uint8Array"' is not assignable to type '"ArrayBuffer"'.
export const count = byteCount(bytes.buffer);jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": [
"*.js"
]
} What did not change
No JSDoc tag changed in 5.9. The release notes do not mention JavaScript files, and every example on this site runs on 5.9 as written.
Upgrade notes
From the behavioral changes:
- Typed arrays passed as
ArrayBuffer: use.bufferor a typed-array parameter, and update@types/node. - Type argument inference changed in some generic calls; a JSDoc cast or a more specific
@paramtype settles them, since.jsfiles cannot pass type arguments. tsc --initoutput is different, so scripts that edit it may need changes.
Gotchas
new Uint8Array([1, 2, 3])is aUint8Array<ArrayBuffer>, so its.bufferis anArrayBuffer; a typed array of unknown origin isUint8Array<ArrayBufferLike>, whose.buffermay be aSharedArrayBuffer.
Sources
- TypeScript 5.9 release notes · TypeScript handbook
- Announcing TypeScript 5.9 · TypeScript blog
Last reviewed 20 October 2026.