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
| Feature | Version | Impact |
|---|---|---|
| NoInfer | 5.4 | Type inference control |
| Closure Narrowing | 5.4 | Code quality improvement |
| Object.groupBy | 5.4 | ES2024 support |
| Inferred Type Predicates | 5.5 | filter etc. improvement |
| isolatedDeclarations | 5.5 | Build speedup |
| Regex Check | 5.5 | Enhanced 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.