Fix TypeScript Strict Mode Errors with Claude Code
The Problem
You enable "strict": true in tsconfig.json and suddenly your project has hundreds of errors:
src/api/users.ts:23:5 - error TS2322: Type 'string | undefined' is not assignable to type 'string'.
src/utils/format.ts:8:22 - error TS7006: Parameter 'item' implicitly has an 'any' type.
src/models/User.ts:12:3 - error TS2564: Property 'email' has no initializer and is not definitely assigned in the constructor.
Strict mode enables multiple compiler flags at once, and the error count can be overwhelming. Fixing them manually across a large codebase takes days.
Quick Fix
Ask Claude Code to fix strict mode errors file by file:
I just enabled strict mode in tsconfig.json. Fix all TypeScript errors
in src/api/users.ts. Use proper types, not 'any'. Add null checks where
needed. Do not use non-null assertions (!) unless the value is provably non-null.
For a bulk approach:
Run tsc --noEmit and show me the error count per file. Then fix each file
starting with the one that has the most errors. Use proper types, not any.
What’s Happening
TypeScript’s "strict": true enables these individual flags:
| Flag | What it catches |
|---|---|
strictNullChecks |
Variables that could be null or undefined used without checking |
noImplicitAny |
Parameters and variables without type annotations |
strictFunctionTypes |
Contravariant function parameter checking |
strictBindCallApply |
Type-checking for bind, call, and apply |
strictPropertyInitialization |
Class properties not initialized in constructor |
noImplicitThis |
this with implicit any type |
alwaysStrict |
Emit "use strict" in every file |
useUnknownInCatchVariables |
Catch variables typed as unknown instead of any |
Each flag catches real bugs. The errors are not false positives. They represent places where your code makes unsafe assumptions about types.
Step-by-Step Fix
Step 1: Enable strict mode incrementally
Instead of enabling everything at once, start with individual flags:
{
"compilerOptions": {
"strict": false,
"strictNullChecks": true
}
}
Ask Claude Code to fix all strictNullChecks errors first, then enable the next flag:
Enable strictNullChecks in tsconfig.json. Run tsc --noEmit and fix
every resulting error. Use proper null guards, not type assertions.
Step 2: Fix strictNullChecks errors
These are the most common and most valuable errors. They catch potential runtime crashes:
// Error: Object is possibly 'undefined'
function getUserName(user?: User): string {
return user.name; // TS2532
}
// Fix with a guard
function getUserName(user?: User): string {
if (!user) {
throw new Error('User is required');
}
return user.name;
}
// Or with a default
function getUserName(user?: User): string {
return user?.name ?? 'Anonymous';
}
For function return types:
// Error: Type 'string | undefined' is not assignable to type 'string'
function findUser(id: string): User {
return users.find(u => u.id === id); // Could be undefined
}
// Fix: update the return type
function findUser(id: string): User | undefined {
return users.find(u => u.id === id);
}
// Or throw if not found
function findUserOrThrow(id: string): User {
const user = users.find(u => u.id === id);
if (!user) {
throw new Error(`User not found: ${id}`);
}
return user;
}
Step 3: Fix noImplicitAny errors
These errors flag parameters and variables without type annotations:
// Error: Parameter 'item' implicitly has an 'any' type
function processItems(items) {
return items.map(item => item.name);
}
// Fix: add type annotations
interface Item {
name: string;
value: number;
}
function processItems(items: Item[]): string[] {
return items.map(item => item.name);
}
Ask Claude Code to infer the correct types from usage:
Fix all noImplicitAny errors in src/utils/. Analyze how each function is
called throughout the codebase to determine the correct types. Do not use 'any'.
Step 4: Fix strictPropertyInitialization errors
Class properties must be initialized in the constructor or declared as optional:
// Error: Property 'email' has no initializer
class User {
name: string; // TS2564
email: string; // TS2564
async init(id: string) {
const data = await fetchUser(id);
this.name = data.name;
this.email = data.email;
}
}
// Fix Option 1: Initialize in constructor
class User {
name: string;
email: string;
constructor(name: string, email: string) {
this.name = name;
this.email = email;
}
}
// Fix Option 2: Use the definite assignment assertion (only when truly needed)
class User {
name!: string; // Only if you guarantee initialization before use
email!: string;
}
// Fix Option 3: Make optional with default
class User {
name: string = '';
email: string = '';
}
Step 5: Fix useUnknownInCatchVariables errors
Catch variables are now unknown instead of any:
// Error: Object is of type 'unknown'
try {
await fetchData();
} catch (error) {
console.log(error.message); // TS18046
}
// Fix: type guard
try {
await fetchData();
} catch (error) {
if (error instanceof Error) {
console.log(error.message);
} else {
console.log('Unknown error:', String(error));
}
}
Step 6: Bulk fix with Claude Code
For large codebases, ask Claude Code to process files systematically:
Run tsc --noEmit 2>&1 | head -200 and categorize the errors by type.
Then fix files in this order:
1. Shared types/interfaces first (src/types/)
2. Utility functions (src/utils/)
3. Data models (src/models/)
4. API handlers (src/api/)
5. Components (src/components/)
This order matters because fixing shared types first resolves cascading errors in dependent files.
Prevention
Always start new projects with strict mode enabled:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}
Add to your CLAUDE.md:
## TypeScript Rules
- strict mode is enabled, do not add @ts-ignore or type assertions
- Use proper null guards, not non-null assertions (!)
- Every function parameter must have an explicit type
- Prefer 'unknown' over 'any' for dynamic values
- Use discriminated unions for complex state types