TypeScript 5.4/5.5 Complete Guide - Type Inference and Performance Evolution

2025.12.02

TypeScript 5.4 and 5.5 bring improvements to type inference, performance enhancements, and new utility types. This article explains these new features with practical code examples.

TypeScript 5.4 New Features

NoInfer Utility Type

NoInfer<T> is a new utility type that suppresses type parameter inference.

// NoInfer - Type Inference Control

// Problem: Unintended type inference
function createState<T>(initial: T, allowed: T[]): T {
  return allowed.includes(initial) ? initial : allowed[0];
}

// Inferred as string, not "red" | "blue"
const state1 = createState("red", ["red", "blue", "green"]);

// Solved with NoInfer
function createStateFixed<T>(initial: T, allowed: NoInfer<T>[]): T {
  return allowed.includes(initial) ? initial : allowed[0];
}

// Correctly inferred as "red" | "blue" | "green"
const state2 = createStateFixed("red", ["red", "blue", "green"]);

// Practical example: Event handlers
type EventMap = {
  click: { x: number; y: number };
  keypress: { key: string };
  scroll: { scrollY: number };
};

function addEventListener<K extends keyof EventMap>(
  event: K,
  handler: (data: NoInfer<EventMap[K]>) => void
): void {
  // implementation
}

// Handler type is not inferred from EventMap[K]
addEventListener("click", (data) => {
  // data is correctly typed as { x: number; y: number }
  console.log(data.x, data.y);
});

Improved Narrowing in Closures

Type narrowing within functions and callbacks has been significantly improved.

// Improved Narrowing in Closures

function processData(value: string | number | null) {
  // TypeScript 5.3 and earlier: Narrowing lost in callbacks
  // TypeScript 5.4: Narrowing is preserved

  if (value === null) {
    return;
  }

  // Here value is string | number

  const handlers = {
    // TS 5.4: Narrowing is preserved in closures
    process: () => {
      // value is recognized as string | number
      if (typeof value === "string") {
        return value.toUpperCase();
      }
      return value.toFixed(2);
    },
    log: () => {
      // Narrowing preserved here too
      console.log(value);
    }
  };

  return handlers.process();
}

// Narrowing after conditional branching
function example(arr: string[] | null) {
  if (arr === null) {
    return;
  }

  // TypeScript 5.4: arr is string[] in map callback too
  arr.map((item) => {
    // arr is guaranteed not to be null
    console.log(arr.length); // OK
    return item.toUpperCase();
  });
}

// More complex example
type Result<T> = { success: true; data: T } | { success: false; error: string };

function processResult<T>(result: Result<T>) {
  if (!result.success) {
    return null;
  }

  // result.data is available
  const transformers = {
    // result.success being true is recognized in closure
    transform: () => {
      return result.data; // TS 5.4: OK
    }
  };

  return transformers.transform();
}

Object.groupBy Type Support

Type support has been added for ES2024’s Object.groupBy and Map.groupBy.

// Object.groupBy - ES2024 Support

interface User {
  id: string;
  name: string;
  role: 'admin' | 'user' | 'guest';
  department: string;
}

const users: User[] = [
  { id: '1', name: 'Alice', role: 'admin', department: 'Engineering' },
  { id: '2', name: 'Bob', role: 'user', department: 'Engineering' },
  { id: '3', name: 'Charlie', role: 'user', department: 'Sales' },
  { id: '4', name: 'Diana', role: 'guest', department: 'Marketing' },
];

// Group by role
const byRole = Object.groupBy(users, (user) => user.role);
// Type: Partial<Record<'admin' | 'user' | 'guest', User[]>>

console.log(byRole.admin); // [{ id: '1', name: 'Alice', ... }]
console.log(byRole.user);  // [{ id: '2', ... }, { id: '3', ... }]

// Group by department
const byDepartment = Object.groupBy(users, (user) => user.department);
// Type: Partial<Record<string, User[]>>

// Map.groupBy - Returns a Map
const byRoleMap = Map.groupBy(users, (user) => user.role);
// Type: Map<'admin' | 'user' | 'guest', User[]>

byRoleMap.forEach((users, role) => {
  console.log(`${role}: ${users.length} users`);
});

// Practical example: Group by date
interface Order {
  id: string;
  amount: number;
  date: Date;
}

function groupOrdersByMonth(orders: Order[]) {
  return Object.groupBy(orders, (order) => {
    const month = order.date.toISOString().slice(0, 7); // "2025-12"
    return month;
  });
}

TypeScript 5.5 New Features

Inferred Type Predicates

Type guards are now automatically inferred from function return values.

// Inferred Type Predicates

// TS 5.4 and earlier: Explicit type predicate required
function isStringOld(value: unknown): value is string {
  return typeof value === 'string';
}

// TS 5.5: Type predicate is automatically inferred
function isString(value: unknown) {
  return typeof value === 'string';
}
// Inferred type: (value: unknown) => value is string

// Utilization in array filter
const mixed: (string | number | null)[] = ['a', 1, null, 'b', 2];

// TS 5.4 and earlier: filter result remains (string | number | null)[]
const stringsOld = mixed.filter((x): x is string => typeof x === 'string');

// TS 5.5: Automatically inferred as string[]
const strings = mixed.filter((x) => typeof x === 'string');
// Type: string[]

// Null removal is also automatic
const notNull = mixed.filter((x) => x !== null);
// Type: (string | number)[]

// Complex example
interface User {
  id: string;
  name: string;
  email?: string;
}

const users: (User | null)[] = [
  { id: '1', name: 'Alice', email: 'alice@example.com' },
  null,
  { id: '2', name: 'Bob' },
];

// Type is automatically narrowed
const validUsers = users.filter((user) => user !== null && user.email);
// Type: User[] (only users with email)

Regular Expression Syntax Checking

Syntax checking for regular expression literals has been added.

// Regular Expression Syntax Checking

// TS 5.5: Invalid regular expressions are compile errors

// Error: Invalid escape sequence
// const invalid1 = /\p/;

// Error: Incomplete character class
// const invalid2 = /[a-/;

// Error: Invalid quantifier
// const invalid3 = /a{}/;

// Valid regular expressions
const email = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const phone = /^\d{3}-\d{4}-\d{4}$/;
const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

// Unicode property escapes (u flag required)
const japanese = /\p{Script=Hiragana}+/u;
const emoji = /\p{Emoji}/u;

// Named capture groups
const datePattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-02'.match(datePattern);
if (match?.groups) {
  const { year, month, day } = match.groups;
  console.log(year, month, day); // "2025", "01", "02"
}

Isolated Declarations

The isolatedDeclarations option enables faster type declaration generation.

// tsconfig.json
{
  "compilerOptions": {
    "isolatedDeclarations": true,
    "declaration": true
  }
}

// With isolatedDeclarations: true,
// function return types and variable types must be explicit

// ❌ Error: Return type required
// export function add(a: number, b: number) {
//   return a + b;
// }

// ✅ OK: Explicit return type
export function add(a: number, b: number): number {
  return a + b;
}

// ❌ Error: Variable type required
// export const config = { port: 3000, host: 'localhost' };

// ✅ OK: Explicit type
export const config: { port: number; host: string } = {
  port: 3000,
  host: 'localhost'
};

// Benefits:
// - Enables parallel builds
// - Can generate .d.ts without type checking
// - Fast builds in monorepos

New Set Method Type Support

Type support for ES2024 Set methods has been added.

// New Set Methods

const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);

// union: Set union
const union = setA.union(setB);
console.log([...union]); // [1, 2, 3, 4, 5, 6]

// intersection: Set intersection
const intersection = setA.intersection(setB);
console.log([...intersection]); // [3, 4]

// difference: Set difference
const difference = setA.difference(setB);
console.log([...difference]); // [1, 2]

// symmetricDifference: Symmetric difference
const symmetricDiff = setA.symmetricDifference(setB);
console.log([...symmetricDiff]); // [1, 2, 5, 6]

// isSubsetOf: Subset check
const subset = new Set([2, 3]);
console.log(subset.isSubsetOf(setA)); // true

// isSupersetOf: Superset check
console.log(setA.isSupersetOf(subset)); // true

// isDisjointFrom: Disjoint check
const setC = new Set([7, 8, 9]);
console.log(setA.isDisjointFrom(setC)); // true

// Practical example: Tag filtering
interface Article {
  id: string;
  title: string;
  tags: Set<string>;
}

function filterByTags(articles: Article[], requiredTags: Set<string>): Article[] {
  return articles.filter(article =>
    requiredTags.isSubsetOf(article.tags)
  );
}

Performance Improvements

Type Checking Speedup

// TypeScript 5.4/5.5 Performance Improvements

// 1. Monomorphized type checking
// - Faster processing of objects with the same shape

// 2. Conditional type optimization
type IsString<T> = T extends string ? true : false;
// Internal caching is improved

// 3. Project reference build optimization
// tsconfig.json
{
  "compilerOptions": {
    "incremental": true,
    "composite": true,
    // Added in 5.5
    "isolatedDeclarations": true
  },
  "references": [
    { "path": "./packages/core" },
    { "path": "./packages/utils" }
  ]
}

// 4. Editor responsiveness improvement
// - Faster auto-import suggestions
// - Improved refactoring operations

Practical Type Patterns

Improved Type Utilities

// Practical Type Patterns

// 1. Safer deep partial
type DeepPartial<T> = T extends object
  ? { [P in keyof T]?: DeepPartial<T[P]> }
  : T;

interface Config {
  server: {
    port: number;
    host: string;
    ssl: {
      enabled: boolean;
      cert: string;
    };
  };
  database: {
    url: string;
    pool: number;
  };
}

// Partial configuration update
function updateConfig(partial: DeepPartial<Config>): Config {
  // Merge logic
  return {} as Config;
}

updateConfig({
  server: {
    ssl: {
      enabled: true
    }
  }
});

// 2. Type-safe event system
type EventMap = {
  'user:login': { userId: string; timestamp: Date };
  'user:logout': { userId: string };
  'order:created': { orderId: string; total: number };
};

class TypedEventEmitter<T extends Record<string, unknown>> {
  private handlers = new Map<keyof T, Set<(data: any) => void>>();

  on<K extends keyof T>(event: K, handler: (data: T[K]) => void): () => void {
    if (!this.handlers.has(event)) {
      this.handlers.set(event, new Set());
    }
    this.handlers.get(event)!.add(handler);

    return () => this.handlers.get(event)?.delete(handler);
  }

  emit<K extends keyof T>(event: K, data: T[K]): void {
    this.handlers.get(event)?.forEach(handler => handler(data));
  }
}

const emitter = new TypedEventEmitter<EventMap>();

// Type-safe event handling
emitter.on('user:login', (data) => {
  // data is { userId: string; timestamp: Date }
  console.log(data.userId, data.timestamp);
});

// 3. Conditional required properties
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
  Pick<T, Exclude<keyof T, Keys>> &
  { [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>> }[Keys];

interface SearchParams {
  query?: string;
  categoryId?: string;
  tags?: string[];
}

// At least one search condition is required
type ValidSearchParams = RequireAtLeastOne<SearchParams, 'query' | 'categoryId' | 'tags'>;

function search(params: ValidSearchParams) {
  // implementation
}

// OK
search({ query: 'test' });
search({ categoryId: '123' });
search({ query: 'test', tags: ['a', 'b'] });

// Error
// search({}); // At least one condition required

Migration Guide

5.3 → 5.4/5.5 Migration

# Upgrade
npm install typescript@latest

# Type check
npx tsc --noEmit

# Common fixes
// 1. Adapting to new narrowing behavior
// Some code may have types correctly inferred,
// making previously required type assertions unnecessary

// Before (TS 5.3)
function example(value: string | null) {
  if (value === null) return;

  const fn = () => {
    // value as string was required
    return (value as string).toUpperCase();
  };
}

// After (TS 5.4+)
function exampleNew(value: string | null) {
  if (value === null) return;

  const fn = () => {
    // Type assertion not needed
    return value.toUpperCase();
  };
}

// 2. Using Object.groupBy
// lib configuration check required
// tsconfig.json
{
  "compilerOptions": {
    "lib": ["ES2024"] // or "ESNext"
  }
}

Summary

TypeScript 5.4/5.5 brings significant improvements to type inference and performance.

Key New Features

FeatureVersionImpact
NoInfer5.4Type inference control
Closure Narrowing5.4Code quality improvement
Object.groupBy5.4ES2024 support
Inferred Type Predicates5.5filter etc. improvement
isolatedDeclarations5.5Build speedup
Regex Check5.5Enhanced error detection

Upgrade Recommendations

  • New projects: Adopt latest version
  • Existing projects: Gradual migration
  • Large projects: Utilize isolatedDeclarations

With TypeScript’s evolution, you can write safer and more efficient code.

← Back to list