languages
August 28, 2026 · 8 min read · 2 views

TypeScript 5.10: Enhanced Type Inference & Strict Null Checking Improvements

TypeScript 5.10 brings smarter type inference, improved null checking, and better performance. Here's what changed and how to upgrade.

TypeScript 5.10: What’s New

TypeScript 5.10 is here, and it’s packed with improvements that make type checking smarter and more reliable. Released in early 2025, this version focuses on better type inference, stricter null handling, and improved performance. For developers working on large codebases or migrating from JavaScript, these changes matter.

In this guide, we’ll walk through the key features, show practical examples, and explain how to upgrade safely.

Why Type Inference Matters

Type inference is TypeScript’s ability to automatically determine what type a variable should have, even when you don’t explicitly write it. The better the inference, the less boilerplate you write—and the fewer runtime errors slip through.

For example, before 5.10, TypeScript sometimes struggled with complex nested types or conditional types. This led to situations where you’d have to add explicit type annotations just to help the compiler understand what you meant.

The Problem: Over-Inferring any

In older versions, when TypeScript couldn’t confidently infer a type, it would sometimes fall back to any. This defeats the purpose of using TypeScript in the first place:

// TypeScript 5.9 behavior
const result = complexFunction();
// result is inferred as 'any' — no type safety!

result.someMethod(); // No error, even if someMethod doesn't exist

The Solution: Smarter Inference in 5.10

TypeScript 5.10 improves inference in several ways:

  1. Better contextual typing — The compiler understands the context better when you assign values to typed variables.
  2. Improved conditional type narrowing — Conditional types now resolve more accurately.
  3. Enhanced generics resolution — Generic type parameters are inferred with fewer ambiguities.

Key Feature: Improved Null Checking

One of the biggest pain points in TypeScript is managing null and undefined. The new version makes strict null checking even more powerful.

Stricter Optional Property Initialization

In TypeScript 5.10, you can be more precise about which properties are required vs. optional:

// TypeScript 5.10 — clearer intent
interface User {
  id: number;
  name: string;
  email?: string;           // Optional — can be undefined
  phone?: string | null;    // Explicitly allows null
  lastLogin: Date | null;   // Required, but can be null
}

const user: User = {
  id: 1,
  name: "Alice",
  lastLogin: null, // OK — property is required, null is allowed
  // email omitted — OK, it's optional
};

// This now correctly types as string | undefined, not just any
const email = user.email;

Better Undefined Detection

When you access optional properties, TypeScript 5.10 now preserves the undefined type more consistently:

interface Post {
  title: string;
  author?: string;
  tags?: string[];
}

const post: Post = { title: "Learning TypeScript" };

// TypeScript 5.10 correctly types this as string | undefined
const author: string | undefined = post.author;

// Accessing nested optional properties is safer
const tagCount = post.tags?.length; // number | undefined

You can validate complex JSON responses with JSON Schema Generator to ensure your types match the actual data shape.

Performance Improvements

TypeScript 5.10 includes optimizations that speed up compilation, especially for large projects:

  • Faster incremental builds — Only re-check files that changed, more efficiently.
  • Reduced memory usage — Type caching is smarter.
  • Parallel checking — Some type checks run in parallel across CPU cores.

For a project with 1000+ files, you might see 10–20% faster build times.

Breaking Changes to Watch For

While 5.10 is mostly backward-compatible, a few changes require attention:

1. Stricter noImplicitAny Behavior

If you have noImplicitAny: true in your tsconfig.json, TypeScript 5.10 will catch more cases where types can’t be inferred:

// This now errors in 5.10 with strict checking
const data = fetchUser(); // ❌ Missing return type

// You must be explicit
const data: User = await fetchUser();

// Or annotate the function
async function fetchUser(): Promise<User> {
  // ...
}

2. Changes to Const Type Parameters

Const type parameters (introduced in 5.10) affect how generic functions resolve types:

// New syntax for preserving literal types
function createTuple<const T extends readonly unknown[]>(items: T): T {
  return items;
}

const tuple = createTuple(["a", 1, true]);
// Type is ["a", 1, true], not (string | number | boolean)[]

If you use inference-heavy patterns, you may need to adjust.

3. Module Resolution Changes

The moduleResolution setting has new options. If you rely on auto-detection, verify your tsconfig.json after upgrading.

Step-by-Step Upgrade Guide

Step 1: Check Current Version

node_modules/.bin/tsc --version

Step 2: Update TypeScript

npm install --save-dev typescript@latest
# or
yarn upgrade --dev typescript@latest
# or
pnpm up -D typescript@latest

Step 3: Run a Type Check

npm run typecheck
# or
npx tsc --noEmit

You may see new errors. This is expected—TypeScript is now catching issues it missed before.

Step 4: Fix Errors Systematically

Start with files that are most critical. Look for:

  • Implicit any — Add explicit type annotations.
  • Null/undefined mismatches — Use optional chaining (?.) or assertions (!).
  • Generic type inference errors — Provide explicit type arguments if needed.

Example fix:

// Before: Error in 5.10
const users = fetchUsers(); // ❌ Implicit any

// After: Explicit type
const users: Promise<User[]> = fetchUsers(); // ✅ OK

Step 5: Update tsconfig.json (if needed)

You may want to enable stricter checks:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "skipLibCheck": true,
    "moduleResolution": "bundler"
  }
}

Step 6: Test Your Application

npm run build
npm run test
npm start

Run your full test suite. TypeScript won’t catch logic errors, but it will catch type violations.

Common Pitfalls and Solutions

Pitfall 1: Over-Relying on Type Assertions

When you encounter a type error, it’s tempting to use as to force a type:

// ❌ Bad: Hiding the real problem
const user = fetchUser() as User;

// ✅ Good: Fix the root cause
const user: User = await fetchUser();

Assertions bypass type checking. Use them only when you’re absolutely certain and TypeScript can’t infer correctly.

Pitfall 2: Forgetting Optional Chaining

With stricter null checks, you need to handle null explicitly:

// ❌ Fails with strictNullChecks
const email = user.email.toLowerCase();

// ✅ Good: Use optional chaining
const email = user.email?.toLowerCase();

// ✅ Or: Check explicitly
if (user.email) {
  const email = user.email.toLowerCase();
}

Pitfall 3: Ignoring Unused Parameters

With noUnusedParameters, TypeScript flags function parameters you don’t use:

// ❌ Error: 'req' is never used
app.get('/users', (req, res) => {
  res.json({ users: [] });
});

// ✅ Good: Prefix with _ if intentional
app.get('/users', (_req, res) => {
  res.json({ users: [] });
});

Practical Example: Migrating a Real Codebase

Let’s say you have a data processing function that needs updating:

Before (TypeScript 5.9)

function processData(data) {
  const items = data.items; // any
  
  return items.map(item => ({
    id: item.id,
    name: item.name,
    email: item.email || "unknown",
  }));
}

After (TypeScript 5.10)

interface DataItem {
  id: number;
  name: string;
  email?: string;
}

interface DataInput {
  items: DataItem[];
}

interface ProcessedItem {
  id: number;
  name: string;
  email: string;
}

function processData(data: DataInput): ProcessedItem[] {
  const items = data.items;
  
  return items.map(item => ({
    id: item.id,
    name: item.name,
    email: item.email ?? "unknown",
  }));
}

You can validate your data structures with JSON Schema Generator to ensure types match actual API responses.

Debugging Type Errors

When type inference goes wrong, use these techniques:

Hover Over Variables in Your IDE

Most IDEs show inferred types on hover. This helps you understand what TypeScript thinks the type is.

Use satisfies for Partial Type Checking

TypeScript 5.10 improves the satisfies operator:

const config = {
  port: 3000,
  host: "localhost",
  debug: true,
} satisfies Record<string, string | number | boolean>;

// config still has literal types: { port: 3000, host: "localhost", ... }
// But it also satisfies the interface

Extract Type Information

For complex types, use helper types:

type ExtractType<T> = T extends Promise<infer U> ? U : never;

type UserType = ExtractType<Promise<User>>; // User

Integration with Development Workflows

In CI/CD Pipelines

Add a type-check step to your CI:

# GitHub Actions example
name: Type Check
on: [push, pull_request]
jobs:
  typecheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: "20"
      - run: npm install
      - run: npm run typecheck

IDE Setup

Make sure your IDE uses the project’s TypeScript version:

VS Code — Add to .vscode/settings.json:

{
  "typescript.tsdk": "node_modules/typescript/lib",
  "typescript.enablePromptUseWorkspaceTsdk": true
}

Pre-commit Hooks

Use Husky to type-check before commits:

npm install husky --save-dev
npx husky install
echo "npm run typecheck" > .husky/pre-commit

Performance Testing

After upgrading, measure build time:

# Measure clean build
rm -rf dist node_modules/.cache
time npm run build

# Measure incremental build
touch src/index.ts
time npm run build

You should see improvements, especially on large projects.

Why It Matters

TypeScript 5.10 closes the gap between what you intend and what your code actually does. Better type inference means:

  1. Fewer runtime bugs — Types catch issues before they reach production.
  2. Better IDE support — Autocomplete and refactoring work more reliably.
  3. Easier maintenance — Future developers understand your intent through types.
  4. Faster development — Less time debugging, more time building.

For teams working with APIs, you can decode JWT tokens and inspect their claims using JWT Decoder to ensure your type definitions match the actual token structure.

Conclusion

TypeScript 5.10 is a meaningful upgrade that improves type safety without requiring major rewrites. Start with a test project, run your type checker, fix errors systematically, and you’ll benefit from a more robust codebase.

Upgrade today, and enjoy a better development experience.

This post was generated with AI assistance and reviewed for accuracy.