TypeScript 5.9 Study Guide (for JavaScript, Node.js, and React Developers)

Last verified: 2026-01-13 Latest stable release line shown on the TypeScript site: TypeScript 5.9 (TypeScript) Latest patch listed on GitHub releases: 5.9.3 (GitHub) TypeScript 5.9 announcement date: 2025-08-01 (Microsoft for Developers)

This document focuses on what’s new/important in TypeScript 5.9, plus the practical configuration + patterns you’ll use in Node.js and React.


Table of contents


1. What “latest TypeScript” means

TypeScript has:

Also: the TypeScript team has been working on a new native toolchain (“TypeScript 7” line) and describes TypeScript 6.0 as a “bridge” release, but that’s roadmap context—not what most projects call “latest stable TypeScript” today. (Microsoft for Developers)


2. Install and verify TypeScript 5.9

npm install --save-dev typescript
npx tsc -v

This is the standard workflow for Node.js projects, and using a lockfile keeps everyone on the same TS version. (TypeScript)

Global install (okay for quick experiments)

npm install -g typescript
tsc -v

The TypeScript download page explicitly calls out global installs as convenient for one-offs but recommends per-project installs for reproducibility. (TypeScript)


3. The biggest change in 5.9: a new default tsc --init

In TypeScript 5.9, running tsc --init now generates a much smaller, more prescriptive tsconfig.json by default, instead of a huge commented file. (TypeScript)

Key defaults from that generated config include (summarized):

Why this matters to you (Node/React dev)

This new default pushes you toward:

But: the new default also includes settings you may want to change depending on whether you’re building an app or a library—especially declaration/declarationMap and types: []. (TypeScript)


4. What’s new in TypeScript 5.9

4.1 import defer

TypeScript 5.9 adds support for the ECMAScript deferred module evaluation proposal via:

import defer * as feature from "./some-feature.js";

Important constraints and behavior:

When it’s useful: deferring expensive initialization or side effects until a feature is actually used (think: optional subsystems, “only on client” code, feature flags, etc.). (TypeScript)

Compare with import() (dynamic import):


4.2 Stable Node 20 mode: --module node20 / --moduleResolution node20

TypeScript 5.9 introduces stable Node 20 modes:

The release notes describe node20 as:

The module reference also notes node20 includes support for require() of ESM in the appropriate interop cases. (TypeScript)

Practical guidance:


4.3 Better DOM API tooltips

TypeScript 5.9 includes summary descriptions for many DOM APIs (based on MDN documentation), improving the in-editor learning experience when you hover DOM types. (TypeScript)


4.4 Better editor hovers

TypeScript 5.9 previews expandable hovers (a “verbosity” control with + / -) in editors like VS Code. (TypeScript)

It also supports a configurable maximum hover length via the VS Code setting js/ts.hover.maximumLength, and increases the default hover length. (TypeScript)


4.5 Performance improvements

Two notable optimizations called out in the 5.9 notes:


4.6 Breaking-ish changes to watch for

lib.d.ts changes: ArrayBuffer vs TypedArrays (including Node Buffer)

TypeScript 5.9 changes built-in types so that ArrayBuffer is no longer a supertype of several TypedArray types, including Node’s Buffer (which is a subtype of Uint8Array). This can surface new errors around BufferSource, ArrayBufferLike, etc. (TypeScript)

Mitigations suggested in the release notes include:

Type argument inference changes

TypeScript 5.9 includes changes meant to fix “leaks” of type variables during inference. This may produce new errors in some codebases, and often the fix is to add explicit type arguments to generic calls. (TypeScript)


5. Modern TS config, explained (Node + React)

This section explains the settings you’re most likely to see (especially if you start from tsc --init in 5.9).

types: why types: [] can surprise you

Common consequences:

verbatimModuleSyntax: predictable import/export output

With verbatimModuleSyntax, TypeScript keeps imports/exports without a type modifier, and drops anything explicitly marked type. This simplifies “import elision” and makes output closer to “what you wrote.” (TypeScript)

This is especially valuable when you want consistency across:

isolatedModules: bundler/transpiler compatibility

isolatedModules warns you about TS patterns that can’t be safely transpiled one file at a time (common with Babel/swc-style transforms). It doesn’t change runtime behavior; it’s a correctness guardrail. (TypeScript)

noUncheckedSideEffectImports: catch typos, but watch asset imports

By default, TypeScript may silently ignore unresolved side-effect imports (import "x";). With noUncheckedSideEffectImports, TypeScript errors if it can’t resolve them. (TypeScript)

If you import assets like CSS, you’ll often solve this by adding an ambient module declaration:

// src/globals.d.ts
declare module "*.css" {}

That exact workaround is recommended in the option docs. (TypeScript)

moduleDetection: "force": treat every file as a module

moduleDetection controls how TS decides whether a file is a “script” or a “module.” The "force" option treats every non-.d.ts file as a module, which avoids a lot of accidental-global-script weirdness. (TypeScript)

noUncheckedIndexedAccess and exactOptionalPropertyTypes: stricter correctness

skipLibCheck: faster builds, lower strictness for .d.ts

skipLibCheck skips type-checking of declaration files. It can speed builds and reduce friction during upgrades, at the cost of some type-system accuracy. (TypeScript)


These are starting points; adjust for your runtime and toolchain.

6.1 Node.js (Node 20, ESM)

Use stable Node 20 semantics.

{
  "compilerOptions": {
    "target": "es2023",
    "module": "node20",
    "moduleResolution": "node20",

    "rootDir": "src",
    "outDir": "dist",

    "strict": true,

    // Important if you started from TS 5.9's `tsc --init` default:
    "types": ["node"],

    "sourceMap": true,

    // For an app you might not need these:
    "declaration": false,
    "declarationMap": false,

    "verbatimModuleSyntax": true,
    "isolatedModules": true,
    "moduleDetection": "force",

    "skipLibCheck": true
  },
  "include": ["src"]
}

Why these choices:

If you do want .d.ts output (e.g., you’re building a library), flip declaration back to true. (TypeScript)


6.2 React (bundler: Vite/Webpack/Next, etc.)

For most React apps, you often want TS to type-check only and let your bundler transpile.

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",

    // Bundler-friendly resolution rules
    "moduleResolution": "bundler",

    "jsx": "react-jsx",

    "strict": true,

    // Let the bundler emit JS
    "noEmit": true,

    // If you started from TS 5.9's init, don't forget types
    // (or remove "types" entirely to use default visibility).
    "types": ["react", "react-dom"],

    "verbatimModuleSyntax": true,
    "isolatedModules": true,
    "moduleDetection": "force",

    "skipLibCheck": true
  },
  "include": ["src"]
}

Notes:


6.3 Libraries (publish types)

If you publish a package, you typically want .d.ts output:

{
  "compilerOptions": {
    "target": "es2020",
    "module": "esnext",
    "moduleResolution": "bundler",

    "rootDir": "src",
    "outDir": "dist",

    "declaration": true,
    "declarationMap": true,

    "strict": true,
    "verbatimModuleSyntax": true,
    "isolatedModules": true,

    "skipLibCheck": true
  },
  "include": ["src"]
}

7. React + TypeScript patterns you’ll use constantly

Strong prop typing without ceremony

type ButtonProps =
  | { variant: "primary"; onClick: () => void }
  | { variant: "link"; href: string };

export function Button(props: ButtonProps) {
  if (props.variant === "link") {
    return <a href={props.href}>Link</a>;
  }
  return <button onClick={props.onClick}>Primary</button>;
}

Why this rocks:

Typed event handlers

function SearchBox() {
  const onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
    console.log(e.currentTarget.value);
  };
  return <input onChange={onChange} />;
}

useState with correct inference

const [count, setCount] = useState(0);      // number inferred
const [user, setUser] = useState<User|null>(null);

Use noUncheckedSideEffectImports safely with CSS/assets

If you enable noUncheckedSideEffectImports, set up ambient modules for assets (CSS, SVG, etc.) so import "./styles.css" stays type-safe. (TypeScript)


8. TypeScript type system refresher (high leverage)

If you already write JS/React daily, these are the TS concepts that pay off fastest:

  1. Union types and narrowing Use unions to model “states” and branches, and let TS narrow with if, switch, in, typeof, etc.

  2. unknown over any unknown forces you to validate before use—ideal for API responses.

  3. Generics (with constraints) Build reusable helpers:

    function first<T>(arr: readonly T[]): T | undefined {
      return arr[0];
    }
    
  4. Mapped + conditional types These power a lot of modern library types (and your own schema/type helpers).

  5. satisfies + as const for “validated literals” Great for config objects where you want literal types but still want validation.


9. Upgrade checklist to 5.9

  1. Upgrade TypeScript (project devDependency) and run a full type-check. (TypeScript)
  2. Watch for ArrayBuffer/TypedArray/Buffer errors caused by lib.d.ts changes. (TypeScript)

    • First try updating @types/node
    • Then consider using typedArray.buffer or more specific typed array parameters (as described in the release notes) (TypeScript)
  3. If you see new generic inference failures, add explicit type arguments where needed. (TypeScript)
  4. If you run tsc --init and copy that config into a React/Node project, double-check:

    • types (because types: [] is restrictive) (TypeScript)
    • whether you really want declaration / declarationMap in an app (TypeScript)

10. What’s coming next (6.0 and the native TypeScript 7 line)

The TypeScript team’s roadmap update (Dec 2025) states:

This is mostly “keep an eye on it” information unless you’re chasing very large-project performance improvements.


11. Practice exercises

  1. Recreate the 5.9 tsc --init defaults, then modify them for:

    • Node 20 backend
    • React bundler frontend (TypeScript)
  2. Try import defer:

    • Create a module with obvious side effects (console.log, expensive initialization).
    • Import it with import defer and confirm evaluation timing. (TypeScript)
  3. Fix a Buffer/ArrayBuffer typing issue:

    • Write a small function that accepts ArrayBuffer.
    • Pass a Uint8Array / Buffer, and fix it via .buffer or more specific typing. (TypeScript)
  4. Turn on noUncheckedSideEffectImports and keep your CSS imports working:

    • Add declare module "*.css" {} in a global .d.ts. (TypeScript)

12. References