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:
- Better contextual typing — The compiler understands the context better when you assign values to typed variables.
- Improved conditional type narrowing — Conditional types now resolve more accurately.
- 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:
- Fewer runtime bugs — Types catch issues before they reach production.
- Better IDE support — Autocomplete and refactoring work more reliably.
- Easier maintenance — Future developers understand your intent through types.
- 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.