Skip to content

TypeScript 5.x Features — Decorators, Const Type Parameters & More

DodaTech 8 min read

In this tutorial, you'll learn about TypeScript 5.x Features. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

TypeScript 5.x introduces ECMAScript standard decorators, explicit resource management, const type parameters, inferred type predicates, iterator helpers, and smarter narrowing. By the end of this tutorial you will understand each major feature and know how to migrate your codebase.

This matters because TypeScript 5.x is the biggest update since 3.0 — it changes how you write decorators, manage resources, and structure generic code. Real-world use: Doda Browser's plugin system uses 5.0 decorators for Dependency Injection in its API layer, and Durga Antivirus Pro uses using declarations to guarantee file handle cleanup in scan pipelines.

timeline
    title TypeScript Version Timeline
    4.x : Template literal types : Variadic tuple types
    5.0 : Decorators (ECMAScript standard) : const type parameters
    5.1 : Improved function return types : Easier null checks
    5.2 : using declarations : Explicit resource management
    5.3 : Import attributes : Narrowing improvements
    5.4 : NoInfer<T> : Preserved narrowing
    5.5 : Inferred type predicates : Control flow narrowing
    5.6 : Iterator helper methods

TypeScript 5.0 — Decorators (ECMAScript Standard)

TypeScript 5.0 ships the Stage 3 ECMAScript decorator proposal, replacing the older experimental decorators. The new decorators work without experimentalDecorators in tsconfig and integrate with standard JavaScript.

function logged(target: any, context: ClassMethodDecoratorContext) {
    const methodName = String(context.name);
    const originalMethod = target;

    function replacementMethod(this: any, ...args: any[]) {
        console.log(`Calling ${methodName} with`, args);
        const result = originalMethod.call(this, ...args);
        console.log(`Called ${methodName} returned`, result);
        return result;
    }

    return replacementMethod;
}

class Calculator {
    @logged
    add(a: number, b: number): number {
        return a + b;
    }
}

const calc = new Calculator();
calc.add(2, 3);
// Calling add with [2, 3]
// Called add returned 5

Key differences from legacy decorators: the context object replaces multiple parameters, and return values replace the target for wrapping. There is no more @decoratorFactory(args) pattern — decorators use closures instead.

TypeScript 5.1 — Improved Function Return Types and Easier Null Checks

Version 5.1 improves type inference for functions returning undefined and makes NaN, Infinity, and other numeric literals behave more predictably in type checks.

function processValue(value: string | null | undefined) {
    if (value) {
        console.log(value.toUpperCase());
    }
}

processValue("hello");
// HELLO
processValue(null);
// (no output, no error)

Prior to 5.1, truthy narrowing on nullable strings sometimes failed in edge cases. The compiler now tracks truthy/falsy checks more accurately across early returns and logical operators.

TypeScript 5.2 — using Declarations and Explicit Resource Management

The using keyword implements the TC39 Explicit Resource Management proposal. It lets you declare a variable whose disposal is tied to the enclosing scope — perfect for file handles, database connections, and locks in Node.js environments.

class FileHandle {
    constructor(private path: string) {
        console.log(`Opened ${path}`);
    }

    read(): string {
        return `content of ${this.path}`;
    }

    [Symbol.dispose]() {
        console.log(`Closed ${this.path}`);
    }
}

function processFile() {
    using file = new FileHandle("config.json");
    console.log(file.read());
}

processFile();
// Opened config.json
// content of config.json
// Closed config.json

The Symbol.dispose method is called when the variable leaves scope — even if an exception is thrown. For async disposal, use await using with Symbol.asyncDispose.

TypeScript 5.3 — Import Attributes and Narrowing Improvements

TypeScript 5.3 stabilizes import attributes (formerly called import assertions) and refines narrowing in generic functions.

import data from "./config.json" with { type: "json" };

console.log(data);

The with { type: "json" } syntax tells the runtime the module is JSON, improving bundler compatibility and security. Narrowing improvements in 5.3 mean the compiler now reduces T | string to string inside typeof x === "string" checks within generic functions.

TypeScript 5.4 — NoInfer<T> and Preserved Narrowing

NoInfer<T> is a new utility type that prevents TypeScript from inferring a type argument from a specific location. This is useful when you want to constrain a generic without creating ambiguity.

function createPair<T extends string, U>(
    first: T,
    second: NoInfer<U>
): [T, U] {
    return [first, second];
}

const result = createPair("admin", { role: "superuser" });
console.log(result);
// ["admin", { role: "superuser" }]

Without NoInfer, TypeScript might widen U to include the literal type of first or produce unexpected inference. Preserved narrowing means variables narrowed inside closures or callbacks retain their narrowed type instead of widening back to the union.

TypeScript 5.5 — Inferred Type Predicates and Control Flow Narrowing

TypeScript 5.5 can infer type predicates from functions that return boolean values. You no longer need to manually write x is Type in many cases.

function isString(value: unknown) {
    return typeof value === "string";
}

function process(value: string | number) {
    if (isString(value)) {
        console.log(value.toUpperCase());
    } else {
        console.log(value.toFixed(2));
    }
}

process("hello");
// HELLO
process(3.1415);
// 3.14

The compiler analyzes the function body and infers value is string as the return type. Control flow narrowing now also handles correlated unions and discriminated properties more accurately.

TypeScript 5.6 — Iterator Helper Methods

TypeScript 5.6 adds type declarations for the Iterator Helper proposal, giving you map, filter, take, drop, and flatMap on iterators.

function* numbers() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
}

const doubled = numbers()
    .map(x => x * 2)
    .filter(x => x > 5);

console.log([...doubled]);
// [6, 8, 10]

Iterator helpers are lazy — they do not create intermediate arrays. This reduces memory pressure in Data Pipelines and aligns TypeScript with the ECMAScript specification.

Migrating Your Codebase to TypeScript 5.x

Start by updating the TypeScript dependency and tsconfig.

npm install typescript@latest

Next, review deprecated compiler options. Remove experimentalDecorators if you are adopting standard decorators. Replace legacy decorator factories with ECMAScript-style decorators. If you use React, note that class components with decorators may need rewrites to functional components or updated Babel presets. Update import assertions (assert syntax) to import attributes (with syntax). Test your build with tsc --noEmit before deploying.

Common migration issues:

  • Legacy @decorator with @param patterns must be rewritten for the new context-based API.
  • Symbol.dispose may conflict with polyfills — ensure your lib includes "ESNext.Disposable" or "ES2023" in tsconfig.
  • Iterators with map/filter require downlevelIteration if targeting ES5 or ES2015.
  • NoInfer<T> is a type-only construct — it does not affect runtime behavior.

Practice Questions

  1. What is the key difference between TypeScript 5.0 decorators and legacy decorators? 5.0 decorators follow the ECMAScript standard, use a context object, do not require experimentalDecorators, and return a replacement function instead of mutating the target.

  2. How does using differ from let or const? using binds disposal to the enclosing scope via Symbol.dispose. When execution leaves the block, the dispose method runs automatically, even on exceptions.

  3. What problem does NoInfer<T> solve? It prevents TypeScript from inferring a generic type argument from a specific usage site, reducing ambiguity when you want inference from one parameter only.

  4. What does "inferred type predicates" mean in TypeScript 5.5? The compiler automatically detects when a boolean function narrows a parameter and infers a type predicate (x is Type) without an explicit return type annotation.

  5. Why are iterator helpers (map, filter) better than array methods? They are lazy — they do not allocate intermediate arrays — and work with any iterable, not just arrays.

Challenge: Write a generic createPool function that uses using to manage a pool of database connections. Each connection should be automatically returned to the pool when the caller finishes, using Symbol.dispose.

Real-world task: Refactor a legacy codebase that uses experimental decorators (@Injectable, @Logger) to use TypeScript 5.0 ECMAScript-style decorators. Update tsconfig, rewrite decorator factories, and verify all tests pass with tsc --noEmit.

FAQ

What TypeScript version should I use for a new project in 2026?

Start with TypeScript 5.6 or later. All features described here are stable, and 5.x tooling is mature across editors, bundlers, and CI pipelines.

Do I need to rewrite all my legacy decorators?

Only if you want to remove experimentalDecorators from tsconfig. Legacy decorators still compile in 5.x, but they will not be updated to match future ECMAScript evolution.

Can I use `using` in browsers or Node.js?

It works in Node.js 22+ and modern Chromium-based browsers with the "ESNext.Disposable" lib. For older environments, use a polyfill for Symbol.dispose.

What happens if I use `NoInfer` with a non-generic type?

NoInfer<T> has no effect on non-generic types. It is a no-op at runtime and only influences type inference during compilation.

How do I enable iterator helpers in older ECMAScript targets?

Set downlevelIteration: true in tsconfig if you target ES5 or ES2015. For ES2020 and above, iterator helpers work without additional flags.

Next Steps

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro