{"slug": "typescript-5-5-the-features-that-actually-matter-for-production-code", "title": "TypeScript 5.5 — The Features That Actually Matter for Production Code", "summary": "TypeScript 5.5 introduces several practical improvements for production code, including automatic inference of type predicates from function implementations (eliminating the need for explicit `v is Type` annotations in filter callbacks) and compile-time validation of regular expressions to catch syntax errors and invalid escape sequences. The update also enhances discriminated union narrowing in array filters, adopts the new `import ... with` syntax for module attributes (deprecating `assert`), and delivers meaningful build speed improvements for large codebases.", "body_md": "# TypeScript 5.5: The Features That Actually Matter for Production Code\n\nTypeScript 5.5 shipped with a mix of headline features and subtle improvements that compound significantly in large codebases. After running it in production for months, here are the features that actually moved the needle for our team.\n\n## The Big One: Inferred Type Predicates\n\nThis sounds small but it's a massive DX improvement. TypeScript can now automatically infer type predicates from function implementations.\n\n### Before 5.5: The Verbose Manual Fix\n\n```\n// Before: TypeScript couldn't narrow the type\nfunction isString(value: unknown): boolean {\n  return typeof value === 'string';\n}\n\nconst values: (string | number)[] = ['hello', 42, 'world', 100];\n\n// You'd have to do this:\nconst strings = values.filter((v) => {\n  if (isString(v)) {\n    return v; // TypeScript still thought v could be number!\n  }\n  return false;\n});\n\n// Or this (more explicit but verbose):\nconst strings = values.filter((v): v is string => isString(v));\n```\n\n### After 5.5: Automatic Inference\n\n```\n// Now TypeScript infers the type predicate automatically\nfunction isString(value: unknown): boolean {\n  return typeof value === 'string';\n}\n\nconst values: (string | number)[] = ['hello', 42, 'world', 100];\n\n// TypeScript now correctly narrows inside the filter callback\nconst strings = values.filter((v) => isString(v));\n// strings is correctly typed as string[]\n\n// Works with more complex predicates\nfunction isNonNullable<T>(value: T): value is NonNullable<T> {\n  return value !== null && value !== undefined;\n}\n\nconst mixed: (string | null | undefined)[] = ['a', null, 'b', undefined, 'c'];\nconst defined = mixed.filter(isNonNullable);\n// defined is string[]\n```\n\n### Where This Compounds: Data Validation Chains\n\n```\n// Before 5.5 required explicit type predicates everywhere\ninterface User {\n  id: string;\n  email: string;\n  role: 'admin' | 'user' | 'guest';\n}\n\nfunction isAdmin(user: User): boolean {\n  return user.role === 'admin';\n}\n\nfunction isActive(user: User): boolean {\n  return user.email.includes('@'); // simplified\n}\n\nconst users: User[] = /* from API */;\n\n// Old way: explicit type predicates\nconst admins = users.filter((u): u is User => isAdmin(u) && isActive(u));\n\n// 5.5 way: inferred predicates chain naturally\nconst admins = users.filter((u) => isAdmin(u) && isActive(u));\n```\n\n## Regular Expression Syntax Checking\n\nTypeScript 5.5 adds type checking for regular expressions. This catches real bugs.\n\n### Caught at Compile Time\n\n``` js\n// This would have been a runtime error before\nconst emailRegex = new RegExp('[a-z+', 'i'); // Syntax error in regex!\n\n// TypeScript 5.5: Error detected at compile time\n// Error: Invalid regular expression: Range out of order in character class\n\n// Another example\nconst dateRegex = new RegExp('(\\\\d{4})-(\\\\d{2})-(\\\\d{2}', 'g'); // Missing closing paren\n// Error: Invalid regular expression: Unterminated group\n```\n\n### The Subtle Case It Catches\n\n``` js\n// Oops: accidentally escaped the wrong character\nconst phoneRegex = new RegExp('\\\\d{3}[-*]\\\\d{3}[-*]\\\\d{4}');\n// Valid! But what if you meant:\n\nconst zipRegex = new RegExp('\\d{5}'); // Missing backslash!\n// TypeScript 5.5 catches this: '\\d' should be '\\\\d' in string\n// Error: Invalid escape sequence in string literal\n```\n\n## Object Types from Array.filter()\n\nWhen you filter an array with a type predicate that narrows to a specific interface, TypeScript now properly infers the output type.\n\n```\ninterface ApiResponse {\n  status: 'success' | 'error';\n  data?: object;\n  error?: string;\n}\n\nconst responses: ApiResponse[] = await fetchAll();\n\n// Before 5.5: You had to manually type the filter result\nconst successful: ApiResponse[] = responses.filter((r) => r.status === 'success');\n// This worked but was verbose for more complex scenarios\n\n// 5.5 handles discriminated unions better\nconst successes = responses.filter((r) => r.status === 'success');\n// successes is (ApiResponse & { status: 'success' })[] but crucially\n// r.data is now properly typed as `object` (not `object | undefined`)\n```\n\n## The import Attributes Fix\n\n``` python\n// Before 5.5: Weird edge cases with import assertions\nimport data from './data.json' assert { type: 'json' };\n\n// 5.5: import attributes syntax (the new standard)\nimport data from './data.json' with { type: 'json' };\n\n// This matters because `assert` is being deprecated in favor of `with`\n// 5.5 ensures you're writing future-proof code\n```\n\n## Performance: Faster Builds\n\nTypeScript 5.5 brought meaningful build speed improvements. In our codebase (~800k lines, 300 packages):\n\n```\nTypeScript 5.4: 45.2s full build\nTypeScript 5.5: 38.7s full build\nImprovement: ~14%\n\nWith --incremental:\nTypeScript 5.4: 12.1s incremental\nTypeScript 5.5: 9.8s incremental\nImprovement: ~19%\n```\n\nThe speedup comes from:\n\n- More efficient type narrowing\n- Smarter stale file detection\n- Better caching of resolved imports\n\n## The new `satisfies`\n\nBehavior\n\n```\n// 5.5 refines what `satisfies` means for union types\ntype Color = 'red' | 'green' | 'blue';\ntype Theme = Record<string, Color | string>;\n\nconst theme = {\n  primary: 'red',\n  secondary: 'blue',\n  custom: '#3498db', // This is valid (string, not Color)\n} satisfies Theme;\n\n// TypeScript now correctly narrows each property\nconst primary: Color = theme.primary; // 'red' (not Color | string)\nconst custom: string = theme.custom; // '#3498db' (not Color | string)\n\n// Before 5.5, you sometimes had to cast even with satisfies\n```\n\n## What Didn't Change (And That's Okay)\n\n### You Still Need Explicit Type Annotations for API Boundaries\n\n```\n// This still requires explicit types - TypeScript can't infer from runtime\nasync function fetchUser(id: string): Promise<User> {\n  // You still need to tell TypeScript what User is\n  const response = await fetch(`/api/users/${id}`);\n  return response.json(); // still any, needs type assertion or schema validation\n}\n\n// Best practice: use a schema validator (zod, typebox)\nimport { z } from 'zod';\n\nconst UserSchema = z.object({\n  id: z.string(),\n  email: z.string().email(),\n  role: z.enum(['admin', 'user', 'guest']),\n});\n\nconst response = await fetch(`/api/users/${id}`);\nconst user = UserSchema.parse(await response.json()); // typed correctly\n```\n\n### Complex Generics Still Need Help\n\n```\n// TypeScript still struggles with complex inference in some cases\nfunction createReducer<S, A extends Action>(\n  initialState: S,\n  handlers: Record<string, (state: S, action: A) => S>\n): Reducer<S, A> {\n  return (state = initialState, action) => {\n    const handler = handlers[action.type];\n    return handler ? handler(state, action) : state;\n  };\n}\n\n// The inference still requires explicit types in some scenarios\n// This is fundamental to how TypeScript's type system works\n```\n\n## Migration Guide: What to Change\n\n### Step 1: Update TypeScript\n\n```\nnpm install typescript@5.5 --save-dev\n# or\npnpm add -D typescript@5.5\n```\n\n### Step 2: Find Type Predicate Boilerplate to Remove\n\nSearch your codebase for `value is`\n\npatterns in filter callbacks:\n\n```\n// This pattern is now unnecessary in most cases:\narray.filter((item): item is Type => predicate(item))\n\n// Can become:\narray.filter(predicate)\n```\n\n### Step 3: Update import assertions to import attributes\n\n``` python\n// Replace:\nimport foo from './foo.json' assert { type: 'json' };\n\n// With:\nimport foo from './foo.json' with { type: 'json' };\n```\n\n### Step 4: Add RegExp Validation (Optional but Recommended)\n\n```\n// Add a lint rule to catch regex errors early\n// ESLint rule: no-invalid-regexp\n\n// Or add a custom rule in your CI:\nconst regex = new RegExp(pattern);\nconsole.log('Regex valid:', regex.source);\n```\n\n## The Bottom Line\n\nTypeScript 5.5 isn't a revolutionary release, but it's a solid incremental improvement. The inferred type predicates alone save hours of boilerplate in large codebases. The performance improvements compound on every build.\n\nThe real value: TypeScript is getting better at reducing the gap between what you write and what TypeScript understands, without requiring increasingly complex type gymnastics.\n\n*Running TypeScript 5.5? What's your experience been? Any surprises — good or bad?*\n\n*Upgrade with confidence: npm install -D typescript@5.5 && npx tsc --version*", "url": "https://wpnews.pro/news/typescript-5-5-the-features-that-actually-matter-for-production-code", "canonical_source": "https://dev.to/zny10289/typescript-55-the-features-that-actually-matter-for-production-code-5di0", "published_at": "2026-05-23 20:22:14+00:00", "updated_at": "2026-05-23 20:32:01.108802+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["TypeScript"], "alternates": {"html": "https://wpnews.pro/news/typescript-5-5-the-features-that-actually-matter-for-production-code", "markdown": "https://wpnews.pro/news/typescript-5-5-the-features-that-actually-matter-for-production-code.md", "text": "https://wpnews.pro/news/typescript-5-5-the-features-that-actually-matter-for-production-code.txt", "jsonld": "https://wpnews.pro/news/typescript-5-5-the-features-that-actually-matter-for-production-code.jsonld"}}