Skip to content

TypeScript 5.x: features that change how you write code

Jamie Liu
Published date:
Edit this post

TypeScript continues to evolve at a rapid pace. The 5.x versions brought changes that go beyond performance improvements: they redefine patterns we have been using for years.

Table of contents

Open Table of contents

Standard decorators (TC39 Stage 3)

Finally. After years with the experimental version, TypeScript 5.0 adopted the standard decorators from TC39. The syntax is similar but the semantics changed quite a bit.

// Class decorator — before (experimental)
@sealed
class OldClass { ... }

// Standard decorator — TS 5.x
function logged<T extends new (...args: unknown[]) => unknown>(
  target: T,
  _ctx: ClassDecoratorContext,
) {
  return class extends target {
    constructor(...args: unknown[]) {
      super(...args);
      console.log(`[LOG] Instance of ${target.name} created`);
    }
  };
}

@logged
class UserService {
  constructor(private db: Database) {}
}decorators.ts

Method and accessor decorators

function measure(_target: unknown, ctx: ClassMethodDecoratorContext) {
  const name = String(ctx.name);
  return function (this: unknown, ...args: unknown[]) {
    const start = performance.now();
    const result = (this as Record<string, Function>)[name](...args); 
    const result = Reflect.apply(
      _target as Function,
      this,
      args 
    ); 
    console.log(`${name} took ${performance.now() - start}ms`);
    return result;
  };
}

class ReportService {
  @measure
  async generatePDF(id: string) {
    /* ... */
  }
}method-decorator.ts

const Type Parameters

Before you needed as const on every call to infer literal tuples. Now you can declare it in the generic:

// Before: inferred as string[]
function head<T>(arr: T[]) {
  return arr[0];
}
head(["a", "b"]); // type: string

// Now: inferred as the exact literal
function head<const T extends readonly unknown[]>(arr: T) {
  return arr[0];
}
head(["a", "b"] as const); // type: "a"
head(["a", "b"]); // type: "a"  ← works without as constconst-type-params.ts

satisfies operator (consolidated)

Introduced in 4.9 but already part of the daily workflow. It allows validating that a value satisfies a type without “widening” it:

type Palette = {
  red: [number, number, number] | string;
  green: [number, number, number] | string;
  blue: [number, number, number] | string;
};

const palette = {
  red: [255, 0, 0],
  green: "#00ff00",
  blue: [0, 0, 255],
} satisfies Palette; 

// Now TypeScript knows that red is a tuple, not string
palette.red.at(0); // ✓ — before it threw an errorsatisfies.ts

Improvements in infer inference

// Extract the return type filtered by constraint
type ReturnIfString<T> = T extends () => infer R extends string
  ? R
  : never;

type A = ReturnIfString<() => "hello">; // "hello"
type B = ReturnIfString<() => number>;  // neverinfer-extends.ts

Performance: --incremental and --composite mode

TS 5.x optimized incremental builds. In large projects the improvement can be up to :

{
  "compilerOptions": {
    "composite": true,
    "incremental": true,
    "tsBuildInfoFile": ".tsbuildinfo",
    "moduleResolution": "bundler"
  }
}tsconfig.json

Tip: combine composite with project references (references) for monorepos. Each package will compile only what changed.

Quick summary

FeatureVersionImpact
Standard decorators5.0High — replaces experimental
const type params5.0Medium — less as const
satisfies4.9 / consolidated in 5.xHigh — more expressive typing
infer ... extends5.xMedium — more precise conditional types
Improved incremental build5.xHigh in monorepos
Previous
Autonomous AI Agents: the new way to build software in 2026
Next
Rust for JavaScript developers: the leap worth taking